package agent

import (
	"context"
	"log/slog"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/hexahost/gamecloud/node-agent/internal/config"
	"github.com/hexahost/gamecloud/node-agent/internal/health"
)

const Version = "0.1.0-phase0"

// Agent coordinates the node agent lifecycle until shutdown.
type Agent struct {
	cfg    *config.Config
	log    *slog.Logger
	health *health.Server
}

// New creates an agent instance from configuration.
func New(cfg *config.Config, log *slog.Logger) *Agent {
	return &Agent{
		cfg:    cfg,
		log:    log,
		health: health.NewServer(cfg.HealthListenAddr, Version),
	}
}

// Run starts the agent loop and blocks until context cancellation or signal.
func (a *Agent) Run(ctx context.Context) error {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	defer signal.Stop(sigCh)

	errCh := make(chan error, 1)
	go func() {
		a.log.Info("health server listening", "addr", a.cfg.HealthListenAddr)
		if err := a.health.ListenAndServe(); err != nil {
			errCh <- err
		}
	}()

	a.health.SetReady(true)
	a.log.Info("agent started",
		"node_id", a.cfg.NodeID,
		"control_plane", a.cfg.ControlPlaneURL,
		"version", Version,
	)

	ticker := time.NewTicker(a.cfg.HeartbeatInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return a.shutdown(ctx.Err())
		case sig := <-sigCh:
			a.log.Info("shutdown signal received", "signal", sig.String())
			return a.shutdown(nil)
		case err := <-errCh:
			return err
		case <-ticker.C:
			a.log.Debug("heartbeat tick",
				"node_id", a.cfg.NodeID,
				"interval", a.cfg.HeartbeatInterval.String(),
			)
		}
	}
}

func (a *Agent) shutdown(reason error) error {
	a.health.SetReady(false)
	a.log.Info("agent shutting down", "grace", a.cfg.ShutdownGracePeriod.String())

	timer := time.NewTimer(a.cfg.ShutdownGracePeriod)
	defer timer.Stop()
	<-timer.C

	if reason != nil && reason != context.Canceled {
		a.log.Warn("shutdown with error", "error", reason)
		return reason
	}
	a.log.Info("agent stopped")
	return nil
}
