package agent

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

	"github.com/hexahost/gamecloud/node-agent/internal/config"
	dockerpkg "github.com/hexahost/gamecloud/node-agent/internal/docker"
	"github.com/hexahost/gamecloud/node-agent/internal/health"
	"github.com/hexahost/gamecloud/node-agent/internal/runtime"
	"github.com/hexahost/gamecloud/node-agent/internal/ws"
)

const Version = "0.2.0-phase2"

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

// New creates an agent instance from configuration.
func New(cfg *config.Config, log *slog.Logger) (*Agent, error) {
	dockerClient, err := dockerpkg.New(cfg.DockerSocket)
	if err != nil {
		return nil, err
	}

	wsClient := ws.NewClient(cfg, log, nil, Version)
	runtimeMgr := runtime.NewManager(dockerClient, wsClient, log)
	wsClient.SetRuntime(runtimeMgr)

	return &Agent{
		cfg:    cfg,
		log:    log,
		health: health.NewServer(cfg.HealthListenAddr, Version),
		docker: dockerClient,
		ws:     wsClient,
	}, nil
}

// 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, 2)
	go func() {
		a.log.Info("health server listening", "addr", a.cfg.HealthListenAddr)
		if err := a.health.ListenAndServe(); err != nil {
			errCh <- err
		}
	}()

	go func() {
		errCh <- a.ws.Run(ctx)
	}()

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

	for {
		select {
		case <-ctx.Done():
			return a.shutdown(nil)
		case sig := <-sigCh:
			a.log.Info("shutdown signal received", "signal", sig.String())
			cancel()
		case err := <-errCh:
			if ctx.Err() != nil {
				return a.shutdown(nil)
			}
			if err != nil {
				return err
			}
		}
	}
}

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

	if a.docker != nil {
		if err := a.docker.Close(); err != nil {
			a.log.Warn("docker client close failed", "error", err)
		}
	}

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