package daemon

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

// InstallOptions configures platform-specific system service registration.
type InstallOptions struct {
	BinPath    string
	ConfigPath string
	// Start requests that the installed service is running before Install returns.
	Start bool
}

// ServiceRegistrationInstaller registers and unregisters the system service.
type ServiceRegistrationInstaller interface {
	// Name returns the service manager identifier (e.g., "systemd", "launchd").
	Name() string
	// Install registers agentteam as a system service.
	Install(opts InstallOptions) error
	// Uninstall removes the agentteam system service from the platform.
	Uninstall() error
}

// ServiceController controls an installed system service.
type ServiceController interface {
	// Start begins running the agentteam system service.
	Start() error
	// Stop gracefully terminates the agentteam system service.
	Stop() error
	// Restart refreshes the running agentteam system service without rewriting its registration.
	Restart() error
	// IsInstalled reports whether the agentteam system service is currently registered.
	IsInstalled() bool
}

// ServiceStatusReporter reports platform service state.
type ServiceStatusReporter interface {
	// Status returns the current runtime status string of the service.
	Status() (string, error)
}

// ServiceInstaller is the full service-management surface returned by NewInstaller.
// Prefer the narrower interfaces above at call sites that only need one capability.
type ServiceInstaller interface {
	ServiceRegistrationInstaller
	ServiceController
	ServiceStatusReporter
}

// ServiceScope selects system-wide or per-user service registration when the
// platform supports both scopes.
type ServiceScope string

const (
	ServiceScopeSystem ServiceScope = "system"
	ServiceScopeUser   ServiceScope = "user"
)

const (
	serviceStatusActive    = "active"
	serviceStatusInactive  = "inactive"
	serviceStatusNotLoaded = "not loaded"
)

// NewInstaller returns the appropriate ServiceInstaller for the current
// operating system (systemd for Linux, launchd for macOS, Task Scheduler for Windows).
func NewInstaller(binPath, configPath string, scope ServiceScope) (ServiceInstaller, error) {
	switch runtime.GOOS {
	case "linux":
		return newSystemdInstaller(binPath, configPath, scope), nil
	case "darwin":
		return newLaunchdInstaller(binPath, configPath), nil
	case "windows":
		return newWindowsInstaller(binPath, configPath), nil
	default:
		return nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS)
	}
}

// --- systemd (Linux) ---

// systemdInstaller manages agentteam as a systemd service on Linux.
type systemdInstaller struct {
	unitName   string
	unitFile   string
	binPath    string
	configPath string
	scope      ServiceScope
}

func newSystemdInstaller(binPath, configPath string, scope ServiceScope) *systemdInstaller {
	scope = normalizeServiceScope(scope)
	unitFile := "/etc/systemd/system/agentteam.service"
	if scope == ServiceScopeUser {
		unitFile = userSystemdUnitFilePath()
	}
	return &systemdInstaller{
		unitName:   "agentteam.service",
		unitFile:   unitFile,
		binPath:    binPath,
		configPath: configPath,
		scope:      scope,
	}
}

func (s *systemdInstaller) Name() string { return "systemd" }

var loginShellPathForUser = func(u string) (string, error) {
	cmd := `for rc in ~/.zshrc ~/.bashrc; do [ -f "$rc" ] && . "$rc" 2>/dev/null; done; echo $PATH`
	out, err := exec.Command("su", "-l", u, "-c", cmd).Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(out)), nil
}

// realHomeDir returns the home directory of the actual user, even when running
// under sudo. It checks SUDO_USER to resolve the original user's home.
func realHomeDir() string {
	if u := os.Getenv("SUDO_USER"); u != "" {
		if usr, err := user.Lookup(u); err == nil {
			return usr.HomeDir
		}
	}
	dir, _ := os.UserHomeDir()
	return dir
}

func currentHomeDir() string {
	dir, _ := os.UserHomeDir()
	return dir
}

func normalizeServiceScope(scope ServiceScope) ServiceScope {
	if scope == ServiceScopeUser {
		return ServiceScopeUser
	}
	return ServiceScopeSystem
}

func userSystemdUnitFilePath() string {
	configDir, err := os.UserConfigDir()
	if err != nil || configDir == "" {
		configDir = filepath.Join(currentHomeDir(), ".config")
	}
	return filepath.Join(configDir, "systemd", "user", "agentteam.service")
}

func (s *systemdInstaller) unitContent() string {
	homeDir := s.homeDir()
	configDir := s.configDir()
	pathEnv := s.pathEnv()

	return fmt.Sprintf(`[Unit]
Description=AgentTeam Desktop Client
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
%sExecStart=%s daemon --owner-kind service
Restart=on-failure
RestartSec=5
%s
%s
%s

[Install]
WantedBy=%s
`, s.userDirective(), s.binPath, systemdEnvironmentLine("HOME", homeDir), systemdEnvironmentLine(profile.ConfigDirEnvKey, configDir), systemdEnvironmentLine("PATH", pathEnv), s.installTarget())
}

func systemdEnvironmentLine(key, value string) string {
	return fmt.Sprintf(`Environment="%s=%s"`, key, escapeSystemdQuotedValue(value))
}

func escapeSystemdQuotedValue(value string) string {
	replacer := strings.NewReplacer(
		`%`, `%%`,
		`\`, `\\`,
		`"`, `\"`,
	)
	return replacer.Replace(value)
}

// resolvePath returns the PATH environment variable from the actual user,
// even when running under sudo. When SUDO_USER is set, it spawns a login
// shell as that user and sources the interactive rc files (.zshrc, .bashrc)
// to obtain the full PATH (including nvm, opencode, etc.).
func resolvePath() string {
	if u := os.Getenv("SUDO_USER"); u != "" {
		if p, err := loginShellPathForUser(u); err == nil {
			if p != "" {
				return prependManagedBinToPath(p)
			}
		}
	}
	currentPath := os.Getenv("PATH")
	if currentPath != "" {
		return prependManagedBinToPath(currentPath)
	}
	return prependManagedBinToPath("/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin")
}

func (s *systemdInstaller) pathEnv() string {
	if s.scope == ServiceScopeUser {
		currentPath := os.Getenv("PATH")
		if currentPath != "" {
			return prependManagedBinToPath(currentPath)
		}
		return prependManagedBinToPath("/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin")
	}
	return resolvePath()
}

func (s *systemdInstaller) homeDir() string {
	if s.scope == ServiceScopeUser {
		return currentHomeDir()
	}
	return realHomeDir()
}

func (s *systemdInstaller) configDir() string {
	configPath := strings.TrimSpace(s.configPath)
	if configPath != "" {
		return filepath.Dir(configPath)
	}
	return filepath.Join(s.homeDir(), "."+profile.AppName)
}

func (s *systemdInstaller) userDirective() string {
	if s.scope == ServiceScopeUser {
		return ""
	}
	effectiveUser := ""
	if u := os.Getenv("SUDO_USER"); u != "" {
		effectiveUser = u
	} else if currentUser, err := user.Current(); err == nil && currentUser.Uid != "0" {
		effectiveUser = currentUser.Username
	}
	if effectiveUser == "" {
		return ""
	}
	if usr, err := user.Lookup(effectiveUser); err == nil {
		return fmt.Sprintf("User=%s\n", usr.Username)
	}
	return ""
}

func (s *systemdInstaller) installTarget() string {
	if s.scope == ServiceScopeUser {
		return "default.target"
	}
	return "multi-user.target"
}

func (s *systemdInstaller) systemctlArgs(args ...string) []string {
	if s.scope != ServiceScopeUser {
		return append([]string{"systemctl"}, args...)
	}
	withUser := []string{"systemctl", "--user"}
	return append(withUser, args...)
}

func (s *systemdInstaller) systemctlCommand(args ...string) *exec.Cmd {
	cmdArgs := s.systemctlArgs(args...)
	return exec.Command(cmdArgs[0], cmdArgs[1:]...)
}

func prependManagedBinToPath(pathValue string) string {
	managedBin := filepath.Join(realHomeDir(), ".agentteam", "bin")
	if pathValue == "" {
		return managedBin
	}
	if strings.Contains(":"+pathValue+":", ":"+managedBin+":") {
		return pathValue
	}
	return managedBin + string(os.PathListSeparator) + pathValue
}

func (s *systemdInstaller) Install(opts InstallOptions) error {
	if opts.BinPath != "" {
		s.binPath = opts.BinPath
	}
	if opts.ConfigPath != "" {
		s.configPath = opts.ConfigPath
	}

	if s.IsInstalled() {
		return fmt.Errorf("agentteam service is already installed (use 'agentteam service uninstall' first)")
	}

	content := s.unitContent()
	if err := os.MkdirAll(filepath.Dir(s.unitFile), 0755); err != nil {
		return fmt.Errorf("failed to create unit directory: %w", err)
	}
	if err := os.WriteFile(s.unitFile, []byte(content), 0644); err != nil {
		return fmt.Errorf("failed to write unit file: %w", err)
	}

	// Ensure the profile and PID directory exists.
	configDir := s.configDir()
	if err := os.MkdirAll(configDir, 0755); err != nil {
		os.Remove(s.unitFile)
		return fmt.Errorf("failed to create directory %s: %w", configDir, err)
	}

	if err := s.systemctlCommand("daemon-reload").Run(); err != nil {
		os.Remove(s.unitFile)
		return fmt.Errorf("failed to reload systemd: %w", err)
	}

	if err := s.systemctlCommand("enable", s.unitName).Run(); err != nil {
		os.Remove(s.unitFile)
		return fmt.Errorf("failed to enable service: %w", err)
	}

	if opts.Start {
		if err := s.Start(); err != nil {
			return err
		}
	}

	return nil
}

func (s *systemdInstaller) Uninstall() error {
	if !s.IsInstalled() {
		slog.Info("agentteam service is not installed")
		return nil
	}

	if err := s.systemctlCommand("disable", s.unitName).Run(); err != nil {
		slog.Warn("failed to disable systemd service during uninstall", "unit", s.unitName, "error", err)
	}
	if err := s.systemctlCommand("stop", s.unitName).Run(); err != nil {
		slog.Warn("failed to stop systemd service during uninstall", "unit", s.unitName, "error", err)
	}

	if err := os.Remove(s.unitFile); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to remove unit file: %w", err)
	}

	if err := s.systemctlCommand("daemon-reload").Run(); err != nil {
		return fmt.Errorf("failed to reload systemd: %w", err)
	}

	return nil
}

func (s *systemdInstaller) Start() error {
	if err := s.systemctlCommand("start", s.unitName).Run(); err != nil {
		return fmt.Errorf("failed to start service: %w", err)
	}
	return nil
}

func (s *systemdInstaller) Stop() error {
	if err := s.systemctlCommand("stop", s.unitName).Run(); err != nil {
		return fmt.Errorf("failed to stop service: %w", err)
	}
	return nil
}

func (s *systemdInstaller) Restart() error {
	if err := s.systemctlCommand("restart", s.unitName).Run(); err != nil {
		return fmt.Errorf("failed to restart service: %w", err)
	}
	return nil
}

func (s *systemdInstaller) IsInstalled() bool {
	_, err := os.Stat(s.unitFile)
	return err == nil
}

func (s *systemdInstaller) Status() (string, error) {
	out, err := s.systemctlCommand("is-active", s.unitName).Output()
	return systemdStatusFromIsActive(out, err), nil
}

func systemdStatusFromIsActive(out []byte, err error) string {
	status := strings.TrimSpace(string(out))
	if status != "" {
		return status
	}
	if err != nil {
		return "inactive"
	}
	return "unknown"
}

// --- launchd (macOS) ---

const (
	launchdServiceLabel       = "cn.pingcap.agentteam.daemon"
	legacyLaunchdServiceLabel = "ai.agentteam.agent"
)

// launchdInstaller manages agentteam as a macOS LaunchAgent via launchctl.
type launchdInstaller struct {
	label      string
	plistFile  string
	binPath    string
	configPath string
}

func newLaunchdInstaller(binPath, configPath string) *launchdInstaller {
	return &launchdInstaller{
		label:      launchdServiceLabel,
		plistFile:  launchdPlistFile(launchdServiceLabel),
		binPath:    binPath,
		configPath: configPath,
	}
}

func (l *launchdInstaller) Name() string { return "launchd" }

var launchdCommandRunner = func(args ...string) error {
	return exec.Command("launchctl", args...).Run()
}

var launchdCommandOutput = func(args ...string) ([]byte, error) {
	return exec.Command("launchctl", args...).CombinedOutput()
}

var (
	launchdSettleTimeout = 3 * time.Second
	launchdPollInterval  = 100 * time.Millisecond
	// launchdExitTimeout is the ExitTimeOut written into the agent plist and the
	// basis for the post-bootout exit wait. Setting it explicitly (rather than
	// relying on launchd's 5s default) lets the daemon actually complete its
	// graceful shutdown, and makes the wait budget deterministic instead of
	// coupled to an OS default. It covers the daemon's worst-case graceful drain
	// (~10s each for gateway ops, the API server, and the dispatcher) plus
	// margin. It is NOT a success criterion: success is the verified
	// post-condition that the service is deregistered, not elapsed time.
	launchdExitTimeout = 35 * time.Second
	// launchdExitWaitMargin is how long unloadLaunchdService waits beyond
	// launchdExitTimeout before force-killing, giving launchd room to deregister.
	launchdExitWaitMargin = 5 * time.Second
	// launchdForceExitTimeout bounds the final deregistration check after a
	// forced kill.
	launchdForceExitTimeout = 5 * time.Second
)

func launchdPlistFile(label string) string {
	homeDir, _ := os.UserHomeDir()
	return filepath.Join(homeDir, "Library", "LaunchAgents", label+".plist")
}

func launchdDomainTargetForUID(uid int) string {
	return fmt.Sprintf("gui/%d", uid)
}

func launchdDomainTarget() string {
	return launchdDomainTargetForUID(os.Getuid())
}

func launchdServiceTargetForUID(uid int, label string) string {
	return fmt.Sprintf("%s/%s", launchdDomainTargetForUID(uid), label)
}

func launchdServiceTarget(label string) string {
	return launchdServiceTargetForUID(os.Getuid(), label)
}

func (l *launchdInstaller) plistContent() string {
	homeDir, _ := os.UserHomeDir()
	configDir := l.configDir()
	logFile := profile.DefaultConfig().LogFile()

	currentPath := resolvePath()

	return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>%s</string>
    <key>ProgramArguments</key>
    <array>
        <string>%s</string>
        <string>daemon</string><string>--owner-kind</string><string>service</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>ExitTimeOut</key>
    <integer>%d</integer>
    <key>StandardOutPath</key>
    <string>%s</string>
    <key>StandardErrorPath</key>
    <string>%s</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>HOME</key>
        <string>%s</string>
        <key>%s</key>
        <string>%s</string>
        <key>PATH</key>
        <string>%s</string>
    </dict>
</dict>
</plist>
`, l.label, l.binPath, int(launchdExitTimeout.Seconds()), logFile, logFile, homeDir, profile.ConfigDirEnvKey, configDir, currentPath)
}

func (l *launchdInstaller) configDir() string {
	configPath := strings.TrimSpace(l.configPath)
	if configPath != "" {
		return filepath.Dir(configPath)
	}
	return filepath.Join(currentHomeDir(), "."+profile.AppName)
}

func (l *launchdInstaller) Install(opts InstallOptions) error {
	if opts.BinPath != "" {
		l.binPath = opts.BinPath
	}
	if opts.ConfigPath != "" {
		l.configPath = opts.ConfigPath
	}

	if l.IsInstalled() {
		return fmt.Errorf("agentteam service is already installed (use 'agentteam service uninstall' first)")
	}
	if err := l.removeLegacyService(); err != nil {
		return err
	}

	plistDir := filepath.Dir(l.plistFile)
	if err := os.MkdirAll(plistDir, 0755); err != nil {
		return fmt.Errorf("failed to create LaunchAgents directory: %w", err)
	}

	content := l.plistContent()
	if err := os.WriteFile(l.plistFile, []byte(content), 0644); err != nil {
		return fmt.Errorf("failed to write plist file: %w", err)
	}

	if !opts.Start {
		return nil
	}

	if err := l.loadAndStart(); err != nil {
		_ = launchdCommandRunner("bootout", launchdServiceTarget(l.label))
		_ = os.Remove(l.plistFile)
		return err
	}

	return nil
}

func (l *launchdInstaller) Uninstall() error {
	removedCurrent, err := l.removeService(l.label, l.plistFile)
	if err != nil {
		return err
	}
	removedLegacy, err := l.removeService(legacyLaunchdServiceLabel, launchdPlistFile(legacyLaunchdServiceLabel))
	if err != nil {
		return err
	}
	if !removedCurrent && !removedLegacy {
		slog.Info("agentteam service is not installed")
	}
	return nil
}

func (l *launchdInstaller) removeLegacyService() error {
	removed, err := l.removeService(legacyLaunchdServiceLabel, launchdPlistFile(legacyLaunchdServiceLabel))
	if err != nil {
		return fmt.Errorf("remove legacy launchd service %q: %w", legacyLaunchdServiceLabel, err)
	}
	if removed {
		slog.Info("removed legacy launchd service", "label", legacyLaunchdServiceLabel)
	}
	return nil
}

func (l *launchdInstaller) removeService(label, plistFile string) (bool, error) {
	plistExists := true
	if _, err := os.Stat(plistFile); os.IsNotExist(err) {
		plistExists = false
	} else if err != nil {
		return false, fmt.Errorf("stat plist file %q: %w", plistFile, err)
	}

	status, err := launchdStatus(label)
	if err != nil {
		return false, err
	}
	loaded := status != serviceStatusNotLoaded
	if loaded {
		if err := unloadLaunchdService(label); err != nil {
			return plistExists || loaded, fmt.Errorf("failed to unload launchd service %q: %w", label, err)
		}
	}

	if plistExists {
		if err := os.Remove(plistFile); err != nil && !os.IsNotExist(err) {
			return true, fmt.Errorf("failed to remove plist file: %w", err)
		}
	}

	return plistExists || loaded, nil
}

func (l *launchdInstaller) loadAndStart() error {
	if err := unloadLaunchdService(l.label); err != nil {
		return fmt.Errorf("prepare launchd service for bootstrap: %w", err)
	}
	if err := bootstrapLaunchdService(l.plistFile); err != nil {
		return err
	}
	if err := kickstartLaunchdService(l.label, false); err != nil {
		return fmt.Errorf("failed to kickstart launchd service: %w", err)
	}
	if err := waitForLaunchdStatus(l.label, serviceStatusActive, launchdSettleTimeout); err != nil {
		return fmt.Errorf("launchd service did not start: %w", err)
	}
	return nil
}

func bootstrapLaunchdService(plistFile string) error {
	if err := launchdCommandRunner("bootstrap", launchdDomainTarget(), plistFile); err != nil {
		return fmt.Errorf("failed to bootstrap launchd service: %w", err)
	}
	return nil
}

// unloadLaunchdService deregisters the launchd job for label and returns only
// once the daemon process has exited. It returns nil exclusively when the
// service is no longer loaded — which by launchd's contract means the process
// is gone — so a subsequent install/start cannot collide with the old instance
// (instance lock, port, receipt). If the graceful shutdown that bootout
// initiates stalls, the still-tracked process is force-killed; only a process
// that cannot be removed at all yields an error.
func unloadLaunchdService(label string) error {
	target := launchdServiceTarget(label)
	if err := launchdCommandRunner("bootout", target); err != nil {
		status, statusErr := launchdStatus(label)
		if statusErr != nil {
			return statusErr
		}
		if status == serviceStatusNotLoaded {
			return nil
		}
		return err
	}
	// bootout accepted. launchd now SIGTERMs the process and, after
	// launchdExitTimeout (declared in the plist), SIGKILLs it; poll until the
	// service is explicitly gone. Success is the verified post-condition, not
	// elapsed time.
	waitErr := waitForLaunchdStatus(label, serviceStatusNotLoaded, launchdExitTimeout+launchdExitWaitMargin)
	if waitErr == nil {
		return nil
	}
	// Only a genuine timeout (the service stayed loaded past the whole graceful
	// budget) justifies a force-kill. A status-read error means the job state is
	// unobservable — surface it and do nothing destructive.
	if !errors.Is(waitErr, errLaunchdStatusTimeout) {
		return fmt.Errorf("wait for launchd service %q to unload: %w", label, waitErr)
	}
	// Timed out: re-confirm the job is actually still loaded before killing — a
	// transient print error must not be mistaken for "still active".
	status, err := launchdStatus(label)
	if err != nil {
		return fmt.Errorf("verify launchd service %q state before force kill: %w", label, err)
	}
	if status == serviceStatusNotLoaded {
		return nil
	}
	// Confirmed still loaded. Ask launchd to force-kill the job by service
	// identity — no raw PID, so no PID-reuse risk and no chance of signaling an
	// unrelated process. A "job already gone" result is fine; the confirmation
	// poll below is the arbiter.
	slog.Warn("launchd service did not exit after bootout; force killing", "label", label)
	if err := launchdCommandRunner("kill", "SIGKILL", target); err != nil {
		slog.Warn("launchd force kill returned error; confirming deregistration anyway",
			"label", label, "error", err)
	}
	// Hard post-condition: the job must be deregistered. If it still is not, the
	// daemon could not be removed, so surface an error rather than mask a dirty
	// state as a successful uninstall.
	if err := waitForLaunchdStatus(label, serviceStatusNotLoaded, launchdForceExitTimeout); err != nil {
		return fmt.Errorf("launchd service %q did not exit after bootout and force kill: %w", label, err)
	}
	return nil
}

func kickstartLaunchdService(label string, kill bool) error {
	args := []string{"kickstart"}
	if kill {
		args = append(args, "-k")
	}
	args = append(args, launchdServiceTarget(label))
	return launchdCommandRunner(args...)
}

// errLaunchdStatusTimeout wraps the timeout returned by waitForLaunchdStatus so
// callers can distinguish "timed out waiting for status" (which may justify a
// force-kill) from a status-read failure (which must never trigger a
// destructive operation while the job state is unobservable).
var errLaunchdStatusTimeout = errors.New("launchd service did not reach expected status within timeout")

func waitForLaunchdStatus(label, want string, timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	for {
		status, err := launchdStatus(label)
		if err != nil {
			return err
		}
		if status == want {
			return nil
		}
		if timeout <= 0 || time.Now().After(deadline) {
			return fmt.Errorf("status %q, want %q: %w", status, want, errLaunchdStatusTimeout)
		}
		time.Sleep(launchdPollInterval)
	}
}

func launchdStatus(label string) (string, error) {
	out, err := launchdCommandOutput("print", launchdServiceTarget(label))
	return launchdStatusFromPrint(out, err)
}

// launchdStatusFromPrint interprets launchctl print output. A service launchd
// reports as absent ("Could not find service") is a clean not-loaded state. Any
// OTHER print failure (permissions, transport, transient) is returned as an
// error so it can never be mistaken for a successful uninstall — success must
// be the explicit, verifiable absence of the job.
func launchdStatusFromPrint(out []byte, err error) (string, error) {
	text := string(out)
	lower := strings.ToLower(text)
	if strings.Contains(lower, "could not find service") {
		return serviceStatusNotLoaded, nil
	}
	if err != nil {
		return "", fmt.Errorf("read launchd service status: %s: %w", strings.TrimSpace(text), err)
	}
	if launchdPrintState(lower) == "running" || launchdPrintHasPositivePID(lower) {
		return serviceStatusActive, nil
	}
	return serviceStatusInactive, nil
}

func launchdPrintState(text string) string {
	for _, line := range strings.Split(text, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "state =") {
			return strings.TrimSpace(strings.TrimPrefix(line, "state ="))
		}
	}
	return ""
}

func launchdPrintPID(text string) int {
	for _, line := range strings.Split(text, "\n") {
		line = strings.TrimSpace(line)
		if !strings.HasPrefix(line, "pid =") {
			continue
		}
		pid, err := strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(line, "pid =")))
		if err == nil && pid > 0 {
			return pid
		}
	}
	return 0
}

func launchdPrintHasPositivePID(text string) bool {
	return launchdPrintPID(text) > 0
}

func (l *launchdInstaller) Start() error {
	status, err := l.Status()
	if err != nil {
		return err
	}
	if status == serviceStatusActive {
		return nil
	}
	if status == serviceStatusNotLoaded {
		if err := bootstrapLaunchdService(l.plistFile); err != nil {
			return fmt.Errorf("failed to bootstrap service before start: %w", err)
		}
	}
	if err := kickstartLaunchdService(l.label, false); err != nil {
		return fmt.Errorf("failed to kickstart service: %w", err)
	}
	if err := waitForLaunchdStatus(l.label, serviceStatusActive, launchdSettleTimeout); err != nil {
		return fmt.Errorf("launchd service did not start: %w", err)
	}
	return nil
}

func (l *launchdInstaller) Stop() error {
	if err := unloadLaunchdService(l.label); err != nil {
		return fmt.Errorf("failed to bootout service: %w", err)
	}
	return nil
}

func (l *launchdInstaller) Restart() error {
	target := launchdServiceTarget(l.label)
	if err := kickstartLaunchdService(l.label, true); err == nil {
		if err := waitForLaunchdStatus(l.label, serviceStatusActive, launchdSettleTimeout); err != nil {
			return fmt.Errorf("launchd service did not restart: %w", err)
		}
		return nil
	} else {
		slog.Warn("failed to kickstart launchd service; falling back to bootout/bootstrap", "target", target, "error", err)
	}

	if err := unloadLaunchdService(l.label); err != nil {
		return fmt.Errorf("failed to bootout launchd service during restart fallback: %w", err)
	}
	if err := bootstrapLaunchdService(l.plistFile); err != nil {
		return fmt.Errorf("failed to bootstrap service after kickstart fallback: %w", err)
	}
	if err := kickstartLaunchdService(l.label, false); err != nil {
		return fmt.Errorf("failed to kickstart service after bootstrap fallback: %w", err)
	}
	if err := waitForLaunchdStatus(l.label, serviceStatusActive, launchdSettleTimeout); err != nil {
		return fmt.Errorf("launchd service did not restart after bootstrap fallback: %w", err)
	}
	return nil
}

func (l *launchdInstaller) IsInstalled() bool {
	_, err := os.Stat(l.plistFile)
	return err == nil
}

func (l *launchdInstaller) Status() (string, error) {
	out, err := launchdCommandOutput("print", launchdServiceTarget(l.label))
	status, statusErr := launchdStatusFromPrint(out, err)
	return status, statusErr
}

// --- Windows Task Scheduler ---

const windowsTaskCommandTimeout = 5 * time.Second

var windowsTaskCommandOutput = func(parent context.Context, args ...string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(parent, windowsTaskCommandTimeout)
	defer cancel()
	out, err := serviceCommandContext(ctx, "schtasks", args...).CombinedOutput()
	if ctx.Err() != nil {
		return out, fmt.Errorf("schtasks %q: %w", strings.Join(args, " "), ctx.Err())
	}
	return out, err
}

// windowsInstaller manages agentteam as a Windows scheduled task via schtasks.
type windowsInstaller struct {
	taskName   string
	binPath    string
	configPath string
}

func newWindowsInstaller(binPath, configPath string) *windowsInstaller {
	return &windowsInstaller{
		taskName:   "AgentTeam",
		binPath:    binPath,
		configPath: configPath,
	}
}

func (w *windowsInstaller) Name() string { return "Windows Task Scheduler" }

func (w *windowsInstaller) Install(opts InstallOptions) error {
	if opts.BinPath != "" {
		w.binPath = opts.BinPath
	}
	if opts.ConfigPath != "" {
		w.configPath = opts.ConfigPath
	}

	if w.IsInstalled() {
		return fmt.Errorf("agentteam service is already installed (use 'agentteam service uninstall' first)")
	}

	args := []string{
		"/Create",
		"/TN", w.taskName,
		"/TR", fmt.Sprintf(`"%s" daemon --owner-kind service`, w.binPath),
		"/SC", "ONLOGON",
		"/RL", "HIGHEST",
		"/F",
	}

	if out, err := windowsTaskCommandOutput(context.Background(), args...); err != nil {
		return fmt.Errorf("failed to create scheduled task: %w\n%s", err, string(out))
	}

	if opts.Start {
		if err := w.Start(); err != nil {
			return err
		}
	}

	return nil
}

func (w *windowsInstaller) Uninstall() error {
	if !w.IsInstalled() {
		slog.Info("agentteam service is not installed")
		return nil
	}

	if out, err := windowsTaskCommandOutput(context.Background(), "/Delete", "/TN", w.taskName, "/F"); err != nil {
		return fmt.Errorf("failed to delete scheduled task: %w\n%s", err, string(out))
	}

	return nil
}

func (w *windowsInstaller) Start() error {
	if out, err := windowsTaskCommandOutput(context.Background(), "/Run", "/TN", w.taskName); err != nil {
		return fmt.Errorf("failed to start scheduled task: %w\n%s", err, string(out))
	}
	return nil
}

func (w *windowsInstaller) Stop() error {
	if out, err := windowsTaskCommandOutput(context.Background(), "/End", "/TN", w.taskName); err != nil {
		if strings.Contains(strings.ToLower(string(out)), "not currently running") {
			return nil
		}
		return fmt.Errorf("failed to stop scheduled task: %w\n%s", err, string(out))
	}
	return nil
}

func (w *windowsInstaller) Restart() error {
	if err := w.Stop(); err != nil {
		return err
	}
	return w.Start()
}

func (w *windowsInstaller) IsInstalled() bool {
	_, err := windowsTaskCommandOutput(context.Background(), "/Query", "/TN", w.taskName)
	return err == nil
}

func (w *windowsInstaller) Status() (string, error) {
	out, err := windowsTaskCommandOutput(context.Background(), "/Query", "/TN", w.taskName, "/FO", "CSV", "/NH")
	if err != nil {
		return "not installed", nil
	}

	fields := parseCSVLine(string(out))
	if len(fields) >= 3 {
		return fields[2], nil
	}
	return "unknown", nil
}

// parseCSVLine is a minimal CSV parser for double-quoted fields,
// used to parse schtasks /Query CSV output.
func parseCSVLine(line string) []string {
	var fields []string
	inQuotes := false
	current := make([]rune, 0, len(line))

	for _, ch := range line {
		switch {
		case ch == '"':
			inQuotes = !inQuotes
		case ch == ',' && !inQuotes:
			fields = append(fields, string(current))
			current = current[:0]
		default:
			current = append(current, ch)
		}
	}
	if len(current) > 0 {
		fields = append(fields, string(current))
	}
	return fields
}
