//go:build windows

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/rcourtman/pulse-go-rewrite/internal/agentupdate"
	"github.com/rcourtman/pulse-go-rewrite/internal/dockeragent"
	"github.com/rcourtman/pulse-go-rewrite/internal/hostagent"
	"github.com/rs/zerolog"
	"golang.org/x/sync/errgroup"
	"golang.org/x/sys/windows/svc"
	"golang.org/x/sys/windows/svc/eventlog"
)

const serviceName = "PulseAgent"

type windowsService struct {
	cfg      Config
	logger   zerolog.Logger
	eventLog *eventlog.Log
}

func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown

	changes <- svc.Status{State: svc.StartPending}

	if ws.eventLog != nil {
		ws.eventLog.Info(1, "Pulse Agent service starting")
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	g, ctx := errgroup.WithContext(ctx)

	// Start Auto-Updater
	updater := agentupdate.New(agentupdate.Config{
		PulseURL:           ws.cfg.PulseURL,
		APIToken:           ws.cfg.APIToken,
		AgentName:          "pulse-agent",
		CurrentVersion:     Version,
		CheckInterval:      1 * time.Hour,
		InsecureSkipVerify: ws.cfg.InsecureSkipVerify,
		Logger:             &ws.logger,
		Disabled:           ws.cfg.DisableAutoUpdate,
	})

	g.Go(func() error {
		updater.RunLoop(ctx)
		return nil
	})

	// Start Host Agent (if enabled)
	if ws.cfg.EnableHost {
		hostCfg := hostagent.Config{
			PulseURL:           ws.cfg.PulseURL,
			APIToken:           ws.cfg.APIToken,
			Interval:           ws.cfg.Interval,
			HostnameOverride:   ws.cfg.HostnameOverride,
			AgentID:            ws.cfg.AgentID,
			AgentType:          "unified",
			AgentVersion:       Version,
			Tags:               ws.cfg.Tags,
			InsecureSkipVerify: ws.cfg.InsecureSkipVerify,
			LogLevel:           ws.cfg.LogLevel,
			Logger:             &ws.logger,
		}

		agent, err := hostagent.New(hostCfg)
		if err != nil {
			ws.logger.Error().Err(err).Msg("Failed to create host agent")
			if ws.eventLog != nil {
				ws.eventLog.Error(1, fmt.Sprintf("Failed to create host agent: %v", err))
			}
			changes <- svc.Status{State: svc.Stopped}
			return true, 1
		}

		g.Go(func() error {
			ws.logger.Info().Msg("Host agent module started")
			return agent.Run(ctx)
		})
	}

	// Start Docker Agent (if enabled)
	if ws.cfg.EnableDocker {
		dockerCfg := dockeragent.Config{
			PulseURL:           ws.cfg.PulseURL,
			APIToken:           ws.cfg.APIToken,
			Interval:           ws.cfg.Interval,
			HostnameOverride:   ws.cfg.HostnameOverride,
			AgentID:            ws.cfg.AgentID,
			AgentType:          "unified",
			AgentVersion:       Version,
			InsecureSkipVerify: ws.cfg.InsecureSkipVerify,
			DisableAutoUpdate:  true,
			LogLevel:           ws.cfg.LogLevel,
			Logger:             &ws.logger,
			SwarmScope:         "node",
			IncludeContainers:  true,
			IncludeServices:    true,
			IncludeTasks:       true,
			CollectDiskMetrics: true,
		}

		agent, err := dockeragent.New(dockerCfg)
		if err != nil {
			ws.logger.Error().Err(err).Msg("Failed to create docker agent")
			if ws.eventLog != nil {
				ws.eventLog.Error(1, fmt.Sprintf("Failed to create docker agent: %v", err))
			}
			changes <- svc.Status{State: svc.Stopped}
			return true, 1
		}

		g.Go(func() error {
			ws.logger.Info().Msg("Docker agent module started")
			return agent.Run(ctx)
		})
	}

	changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
	ws.logger.Info().
		Str("version", Version).
		Str("pulse_url", ws.cfg.PulseURL).
		Bool("host_agent", ws.cfg.EnableHost).
		Bool("docker_agent", ws.cfg.EnableDocker).
		Msg("Pulse Agent service is running")
	if ws.eventLog != nil {
		ws.eventLog.Info(1, fmt.Sprintf("Pulse Agent started (URL: %s, Host: %v, Docker: %v)", ws.cfg.PulseURL, ws.cfg.EnableHost, ws.cfg.EnableDocker))
	}

	// Channel to receive errgroup completion
	doneChan := make(chan error, 1)
	go func() {
		doneChan <- g.Wait()
	}()

	// Service control loop
loop:
	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Interrogate:
				changes <- c.CurrentStatus
			case svc.Stop, svc.Shutdown:
				ws.logger.Info().Uint32("command", uint32(c.Cmd)).Msg("Received service control command")
				if ws.eventLog != nil {
					ws.eventLog.Info(1, "Pulse Agent received stop command")
				}
				changes <- svc.Status{State: svc.StopPending}
				cancel()
				break loop
			default:
				ws.logger.Warn().Uint32("command", uint32(c.Cmd)).Msg("Unexpected service control command")
			}
		case err := <-doneChan:
			if err != nil && err != context.Canceled {
				ws.logger.Error().Err(err).Msg("Agent error")
				if ws.eventLog != nil {
					ws.eventLog.Error(1, fmt.Sprintf("Pulse Agent error: %v", err))
				}
				changes <- svc.Status{State: svc.Stopped}
				return true, 1
			}
			break loop
		}
	}

	// Wait for agents to stop gracefully (with timeout)
	shutdownTimeout := time.NewTimer(10 * time.Second)
	defer shutdownTimeout.Stop()

	select {
	case <-doneChan:
		ws.logger.Info().Msg("Agents stopped gracefully")
		if ws.eventLog != nil {
			ws.eventLog.Info(1, "Pulse Agent stopped gracefully")
		}
	case <-shutdownTimeout.C:
		ws.logger.Warn().Msg("Agent shutdown timeout, forcing stop")
		if ws.eventLog != nil {
			ws.eventLog.Warning(1, "Pulse Agent shutdown timeout")
		}
	}

	changes <- svc.Status{State: svc.Stopped}
	return false, 0
}

// runAsWindowsService checks if we're running as a Windows service and handles it.
// Returns a special error to indicate the service ran (and main should exit),
// returns nil if not running as a service (main should continue normally),
// or returns an error if something failed.
func runAsWindowsService(cfg Config, logger zerolog.Logger) (ranAsService bool, err error) {
	isService, err := svc.IsWindowsService()
	if err != nil {
		return false, fmt.Errorf("failed to determine if running as service: %w", err)
	}

	if !isService {
		return false, nil
	}

	logger.Info().Msg("Running as Windows service")

	// Open Windows Event Log (best effort)
	elog, err := eventlog.Open(serviceName)
	if err != nil {
		logger.Warn().Err(err).Msg("Could not open Windows Event Log, continuing without it")
		elog = nil
	}
	defer func() {
		if elog != nil {
			elog.Close()
		}
	}()

	ws := &windowsService{
		cfg:      cfg,
		logger:   logger,
		eventLog: elog,
	}

	err = svc.Run(serviceName, ws)
	if err != nil {
		if elog != nil {
			elog.Error(1, fmt.Sprintf("Failed to run service: %v", err))
		}
		return true, fmt.Errorf("failed to run Windows service: %w", err)
	}

	// Service ran successfully and exited
	os.Exit(0)
	return true, nil // unreachable, but required for compilation
}
