package cmd

import (
	"fmt"
	"strings"
	"time"

	"github.com/spf13/cobra"

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

const defaultAgentTeamServerURL = "https://agentteam.pingcap.com"

// daemonOpts holds flags for the daemon command.
var daemonOpts struct {
	host      string
	port      int
	ownerKind string
	ownerID   string
	ownerPID  int

	connectType        string
	agentteamServerURL string
	agentteamAPIKey    string
}

// daemonCmd runs the agentteam daemon in the foreground.
var daemonCmd = &cobra.Command{
	Use:   "daemon",
	Short: "Start the AgentTeam daemon in the foreground",
	Long: `Start the headless AgentTeam daemon process in the foreground.

The daemon connects this machine to AgentTeam, runs local agent runtimes, and hosts
the authenticated local API. Use Ctrl+C or SIGTERM to stop.

For background operation, install as a system service instead:
  agentteam service install

Examples:
  agentteam daemon                                             Start the daemon with default config
  agentteam daemon --server-url <url> --api-key <key>         Save AgentTeam machine settings and start if needed
  agentteam daemon --host 127.0.0.1 --port 9090               Use an explicit loopback port`,
	RunE: func(cmd *cobra.Command, args []string) error { return runDaemonCommand() },
}

var daemonStatus = daemon.NewStatus
var stopUnhealthyProcess = daemon.StopUnhealthyProcess
var daemonFlagLookup = func(string) bool { return false }

func runDaemonCommand() error {
	connectType := resolvedDaemonConnectType()
	if connectType == "" {
		return runDaemonForeground()
	}

	if connectType != "agentteam" {
		return fmt.Errorf("daemon connect currently supports only --connect agentteam")
	}

	status := daemonStatus()

	op, err := getOperator()
	if err != nil {
		return err
	}

	if strings.TrimSpace(daemonOpts.agentteamAPIKey) == "" {
		return fmt.Errorf("agentteam daemon requires --api-key when saving AgentTeam machine settings")
	}
	resolved, err := resolveManualAgentTeamAuth(daemonOpts.agentteamServerURL, daemonOpts.agentteamAPIKey)
	if err != nil {
		return err
	}
	saveResult, err := upsertAgentTeamAuth(op.Servers(), *resolved)
	if err != nil {
		return err
	}

	fmt.Println("AgentTeam auth ready.")
	fmt.Printf("ID:        %s\n", saveResult.Server.ID)
	fmt.Printf("Server:    %s\n", agentteamChannelServerURL(saveResult.Server))
	fmt.Printf("Workspace: %s\n", agentteamChannelWorkspaceID(saveResult.Server))
	fmt.Println()
	if status.Running && status.Healthy {
		return nil
	}

	fmt.Println("Starting AgentTeam daemon...")
	return runDaemonForeground()
}

func resolvedDaemonConnectType() string {
	connectType := strings.ToLower(strings.TrimSpace(daemonOpts.connectType))
	if connectType != "" && connectType != "agentteam" {
		return connectType
	}

	if strings.TrimSpace(daemonOpts.agentteamAPIKey) != "" || daemonAgentTeamFlagsChanged() {
		return "agentteam"
	}

	return ""
}

func daemonAgentTeamFlagsChanged() bool {
	return daemonFlagChanged("server-url") ||
		daemonFlagChanged("api-key") ||
		daemonFlagChanged("agentteam.server-url") ||
		daemonFlagChanged("agentteam.api-key")
}

func daemonFlagChanged(name string) bool {
	return daemonFlagLookup(name)
}

var runDaemonForeground = runDaemonForegroundImpl
var runForegroundDaemon = func(options daemon.RunOptions) error {
	return daemon.New(profile.Path()).Run(options)
}

func runDaemonForegroundImpl() error {
	status := daemonStatus()
	if status.Running {
		if !status.Healthy {
			if err := stopUnhealthyProcess(status.PID, 5*time.Second); err != nil {
				return fmt.Errorf("agentteam daemon process exists but local API is unhealthy (PID: %d): %w", status.PID, err)
			}
			status = daemonStatus()
		}
		if status.Running && status.Healthy {
			return nil
		}
	}
	ownerKind := daemoncontrol.OwnerKind(strings.ToLower(strings.TrimSpace(daemonOpts.ownerKind)))
	switch ownerKind {
	case daemoncontrol.OwnerKindCLI, daemoncontrol.OwnerKindService, daemoncontrol.OwnerKindElectron:
	default:
		return fmt.Errorf("unsupported daemon owner kind %q (want cli, service, or electron)", daemonOpts.ownerKind)
	}
	return runForegroundDaemon(daemon.RunOptions{
		OwnerBootstrap: daemon.OwnerBootstrap{
			Kind: ownerKind,
			ID:   strings.TrimSpace(daemonOpts.ownerID),
			PID:  daemonOpts.ownerPID,
		},
		BindIntent: daemon.BindIntent{
			Host: strings.TrimSpace(daemonOpts.host),
			Port: daemonOpts.port,
		},
	})
}

func init() {
	daemonCmd.Flags().StringVar(&daemonOpts.host, "host", "", "server loopback bind address")
	daemonCmd.Flags().IntVar(&daemonOpts.port, "port", 0, "server loopback listen port (0 selects dynamically)")
	daemonCmd.Flags().StringVar(&daemonOpts.ownerKind, "owner-kind", string(daemoncontrol.OwnerKindCLI), "daemon owner kind: cli, service, or electron")
	daemonCmd.Flags().StringVar(&daemonOpts.ownerID, "owner-id", "", "daemon owner ID (required for electron)")
	daemonCmd.Flags().IntVar(&daemonOpts.ownerPID, "owner-pid", 0, "daemon owner PID (required for electron)")
	_ = daemonCmd.Flags().MarkHidden("owner-kind")
	_ = daemonCmd.Flags().MarkHidden("owner-id")
	_ = daemonCmd.Flags().MarkHidden("owner-pid")
	daemonCmd.Flags().StringVar(&daemonOpts.connectType, "connect", "", "machine connect target (currently only: agentteam)")
	daemonCmd.Flags().StringVar(&daemonOpts.agentteamServerURL, "server-url", defaultAgentTeamServerURL, "server URL for saving AgentTeam machine settings")
	daemonCmd.Flags().StringVar(&daemonOpts.agentteamAPIKey, "api-key", "", "API key for saving AgentTeam machine settings")
	daemonCmd.Flags().StringVar(&daemonOpts.agentteamServerURL, "agentteam.server-url", defaultAgentTeamServerURL, "alias for --server-url")
	daemonCmd.Flags().StringVar(&daemonOpts.agentteamAPIKey, "agentteam.api-key", "", "alias for --api-key")
	_ = daemonCmd.Flags().MarkHidden("agentteam.server-url")
	_ = daemonCmd.Flags().MarkHidden("agentteam.api-key")
	daemonFlagLookup = func(name string) bool {
		flag := daemonCmd.Flags().Lookup(name)
		return flag != nil && flag.Changed
	}
}
