//go:build windows

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"github.com/example/sessionguard/internal/agent"
	"github.com/example/sessionguard/internal/config"
	"golang.org/x/sys/windows/svc"
	"golang.org/x/sys/windows/svc/mgr"
)

const serviceName = "SessionGuardAgent"

func defaultConfigPath() string { return `C:\ProgramData\SessionGuard\agent.json` }

func platformMain(action, path string) error {
	switch action {
	case "install":
		return installService(path)
	case "uninstall":
		return uninstallService()
	case "start":
		return startService()
	case "stop":
		return stopService()
	case "run":
		return runAsService(path)
	case "":
		isSvc, err := svc.IsWindowsService()
		if err == nil && isSvc {
			return runAsService(path)
		}
		return runConsole(path)
	default:
		return fmt.Errorf("unknown service action %q", action)
	}
}

func setupLog(dataDir string) {
	_ = os.MkdirAll(dataDir, 0o700)
	f, err := os.OpenFile(filepath.Join(dataDir, "agent.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
	if err == nil {
		log.SetOutput(f)
	}
	log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.LUTC)
}
func loadApp(path string) (*agent.App, error) {
	cfg, err := config.LoadAgent(path)
	if err != nil {
		return nil, err
	}
	setupLog(cfg.DataDir)
	return agent.New(cfg)
}
func runConsole(path string) error {
	app, err := loadApp(path)
	if err != nil {
		return err
	}
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()
	return app.Run(ctx)
}

type serviceHandler struct{ path string }

func (h *serviceHandler) Execute(args []string, requests <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
	const accepts = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptSessionChange
	status <- svc.Status{State: svc.StartPending}
	app, err := loadApp(h.path)
	if err != nil {
		return false, 1
	}
	ctx, cancel := context.WithCancel(context.Background())
	done := make(chan error, 1)
	go func() { done <- app.Run(ctx) }()
	status <- svc.Status{State: svc.Running, Accepts: accepts}
	for {
		select {
		case c := <-requests:
			switch c.Cmd {
			case svc.Interrogate:
				status <- c.CurrentStatus
			case svc.SessionChange:
				// Windows sends SERVICE_CONTROL_SESSIONCHANGE for logon, logoff, connect,
				// disconnect, lock and unlock. Wake the agent worker without doing file or
				// network I/O inside the Service Control Manager callback.
				app.NotifySessionChange()
			case svc.Stop, svc.Shutdown:
				status <- svc.Status{State: svc.StopPending}
				cancel()
				select {
				case <-done:
				case <-time.After(10 * time.Second):
				}
				return false, 0
			}
		case err := <-done:
			if err != nil {
				log.Printf("service stopped: %v", err)
				return false, 1
			}
			return false, 0
		}
	}
}
func runAsService(path string) error { return svc.Run(serviceName, &serviceHandler{path: path}) }
func installService(path string) error {
	m, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer m.Disconnect()
	if s, err := m.OpenService(serviceName); err == nil {
		s.Close()
		return fmt.Errorf("service already exists")
	}
	exe, err := os.Executable()
	if err != nil {
		return err
	}
	s, err := m.CreateService(serviceName, exe, mgr.Config{DisplayName: "SessionGuard Agent", Description: "SessionGuard Terminal Server Agent", StartType: mgr.StartAutomatic}, "-config", path, "-service", "run")
	if err != nil {
		return err
	}
	defer s.Close()
	return nil
}
func uninstallService() error {
	m, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer m.Disconnect()
	s, err := m.OpenService(serviceName)
	if err != nil {
		return err
	}
	defer s.Close()
	return s.Delete()
}
func startService() error {
	m, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer m.Disconnect()
	s, err := m.OpenService(serviceName)
	if err != nil {
		return err
	}
	defer s.Close()
	return s.Start()
}
func stopService() error {
	m, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer m.Disconnect()
	s, err := m.OpenService(serviceName)
	if err != nil {
		return err
	}
	defer s.Close()
	_, err = s.Control(svc.Stop)
	return err
}
