package cmd

import (
	"bufio"
	"fmt"
	"io"
	"log/slog"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/spf13/cobra"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeconfig"
	"github.com/tidbcloud/AgentTeam/desktop/internal/daemon"
	"github.com/tidbcloud/AgentTeam/desktop/internal/doctor"
	"github.com/tidbcloud/AgentTeam/desktop/internal/installpath"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

// onboardCmd initializes agentteam with an interactive setup flow.
var onboardCmd = &cobra.Command{
	Use:   "onboard",
	Short: "Interactive first-time setup",
	Long: `First-time setup wizard for agentteam.

Creates the default configuration directory and files, detects
available AI coding agent runtimes (builtin, opencode, claude, codex, kimi, pi),
optionally installs agentteam as a service, and runs diagnostics to verify the installation.

Use --runtime to skip the full wizard and directly enter the runtime
detection view.

Examples:
  agentteam onboard                    Run the interactive setup wizard
  agentteam onboard --runtime          Show runtime detection status`,
	RunE: func(cmd *cobra.Command, args []string) error {
		runtimeOnly, _ := cmd.Flags().GetBool("runtime")
		if runtimeOnly {
			return runRuntimeSetup()
		}
		return runOnboard()
	},
}

func init() {
	onboardCmd.Flags().Bool("runtime", false, "skip full wizard and show runtime detection status")
}

const onboardWelcomeDetail = "it connects to AgentTeam, runs local agent runtimes, and exposes the authenticated local API"

func runOnboard() error {
	slog.Info("welcome to agentteam")
	slog.Info("agentteam is the AgentTeam daemon and command-line client for this machine",
		"detail", onboardWelcomeDetail)

	// --- Config setup ---
	if err := ensureConfigDir(); err != nil {
		return fmt.Errorf("failed to create config directory: %w", err)
	}
	printStatus("Config directory", true, profile.ConfigDir())

	if err := ensureConfigFile(); err != nil {
		return fmt.Errorf("failed to create config file: %w", err)
	}
	printStatus("Config file", true, profile.Path())

	if probe := runtimeconfig.ProbeRuntimeBinary(runtimeconfig.TypeBuiltin, ""); !probe.Capable {
		printStatus("Builtin OpenCode runtime", false, probe.Message)
	} else {
		printStatus("Builtin OpenCode runtime", true, probe.Path)
	}

	// --- Check and setup runtimes ---
	if err := ensureRuntimes(); err != nil {
		slog.Warn("warning", "error", err)
	} else {
		slog.Info("runtimes configured", "status", "OK")
	}

	// --- Ask: enable daemon ---
	enableDaemon := promptYesNo("Enable auto-start daemon?", true)

	if !enableDaemon {
		printManualStart()
		return nil
	}

	// --- Install service ---
	exe, err := os.Executable()
	if err != nil {
		return fmt.Errorf("failed to determine executable path: %w", err)
	}
	managedExe, err := installpath.EnsureManagedBinary(exe)
	if err != nil {
		return fmt.Errorf("prepare managed agentteam binary for onboarding: %w", err)
	}
	if err := installpath.EnsureShellPath(); err != nil {
		slog.Warn("failed to update shell PATH for managed agentteam binary", "error", err)
	}

	scope := defaultOnboardServiceScope(runtime.GOOS, os.Geteuid())
	installer, err := daemon.NewInstaller(managedExe, profile.Path(), scope)
	if err != nil {
		return err
	}

	slog.Info("installing agentteam as service", "name", installer.Name(), "scope", scope)

	if err := installer.Install(daemon.InstallOptions{BinPath: managedExe, ConfigPath: profile.Path(), Start: true}); err != nil {
		slog.Warn("failed to install service", "error", err)
		slog.Info("you can try installing manually", "command", installCommandHint(scope))
		return nil
	}

	slog.Info("service installed successfully", "via", installer.Name(), "scope", scope)

	if _, err := waitForHealthyRunningDaemon(15 * time.Second); err != nil {
		slog.Warn("daemon did not report healthy after start", "error", err)
	}

	// --- Run diagnostics ---
	results := doctor.RunChecks(checkDaemonStatus)
	printCheckResults(results)

	hasFail := false
	for _, r := range results {
		if r.Status == doctor.StatusFail {
			hasFail = true
			break
		}
	}

	if hasFail {
		slog.Warn("some checks failed", "detail", "run 'agentteam doctor' for details")
		return nil
	}

	slog.Info("agentteam daemon is running")

	return nil
}

func defaultOnboardServiceScope(goos string, euid int) daemon.ServiceScope {
	if goos == "darwin" {
		return daemon.ServiceScopeUser
	}
	if goos == "linux" && euid != 0 {
		return daemon.ServiceScopeUser
	}
	return daemon.ServiceScopeSystem
}

// runRuntimeSetup shows runtime detection status directly.
func runRuntimeSetup() error {
	fmt.Println()
	fmt.Println("=== Runtime Setup ===")
	fmt.Println()

	printRuntimeDetectionStatus(os.Stdout)
	return nil
}

func printRuntimeDetectionStatus(w io.Writer) {
	mgr := profile.New(profile.ConfigDir())
	results := mgr.Runtimes().RefreshDetect()

	fmt.Fprintln(w)
	fmt.Fprintln(w, "Runtime detection:")
	for _, result := range results {
		status := "not found"
		if result.Capable {
			status = "usable"
		} else if result.Installed {
			status = "unsupported"
		}
		detail := result.Path
		if detail == "" {
			detail = result.Message
		}
		if detail == "" {
			detail = result.Binary
		}
		fmt.Fprintf(w, "  - %s: %s (%s)\n", result.Type, status, detail)
	}
	fmt.Fprintln(w)
	fmt.Fprintln(w, "AgentTeam Desktop no longer installs or upgrades external runtimes. Builtin OpenCode is shipped with AgentTeam Desktop; external OpenCode, Claude Code, or Codex can still be used when their binaries are manually configured or available on PATH.")
}

func ensureConfigDir() error {
	return profile.EnsureDir()
}

func ensureConfigFile() error {
	configPath := profile.Path()
	if _, err := os.Stat(configPath); err == nil {
		return nil
	}

	cfg := profile.DefaultConfig()
	cfg.Port = 0
	return profile.Save(cfg, configPath)
}

func printStatus(label string, ok bool, detail string) {
	status := "OK"
	if !ok {
		status = "WARN"
	}
	slog.Info(label, "status", status, "detail", detail)
}

// promptYesNo asks a yes/no question and returns the answer.
// defaultYes controls the default when the user presses Enter.
func promptYesNo(question string, defaultYes bool) bool {
	prompt := " [Y/n]: "
	if !defaultYes {
		prompt = " [y/N]: "
	}
	fmt.Print(question + prompt)

	reader := bufio.NewReader(os.Stdin)
	line, _ := reader.ReadString('\n')
	line = strings.TrimSpace(strings.ToLower(line))

	if line == "" {
		return defaultYes
	}
	return line == "y" || line == "yes"
}

func printManualStart() {
	scope := defaultOnboardServiceScope(runtime.GOOS, os.Geteuid())
	slog.Info("you chose not to install the daemon as a service", "scope", scope)
	slog.Info("you can start agentteam manually", "command", "agentteam daemon")
	slog.Info("to install as a service later", "command", installCommandHint(scope))
}

// ensureRuntimes checks if any runtime is available. It does not install or
// upgrade external runtimes; builtin OpenCode is expected to ship with AgentTeam Desktop.
func ensureRuntimes() error {
	mgr := profile.Default()
	runtimes := mgr.Runtimes().All()
	if len(runtimes) > 0 {
		for _, rt := range runtimes {
			printStatus(fmt.Sprintf("  Runtime %q", rt.Type), true, fmt.Sprintf("%s, binary: %s", rt.Type, rt.Binary))
		}
		return nil
	}

	printRuntimeDetectionStatus(os.Stdout)
	slog.Warn("no usable AI coding agent runtime detected")
	slog.Info("AgentTeam Desktop includes builtin OpenCode; if it is missing, reinstall or update AgentTeam Desktop")
	slog.Info("external runtimes remain available as an advanced manual PATH or binary configuration")
	return nil
}
