// Command drover is the entry point for the Discord proxy CLI.
package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"

	"git.okcu.io/root/drover-go/internal/gui"
	"git.okcu.io/root/drover-go/internal/updater"
)

// Build-time variables, populated via -ldflags "-X main.Version=... -X main.Commit=... -X main.BuildDate=...".
var (
	Version   = "dev"
	Commit    = "dev"
	BuildDate = "dev"
)

// configPath is the path to the TOML config file, set via the --config global flag.
// Reserved for use in later phases.
var configPath string

func main() {
	// On Windows the binary is linked with -H=windowsgui so a double-click
	// doesn't flash a console window. When the user runs us from cmd or
	// PowerShell we still want stdout/stderr to land in their terminal —
	// AttachConsole(ATTACH_PARENT_PROCESS) wires that up. No-op elsewhere.
	attachToParentConsole()

	// Detect if we need admin for the command in os.Args[1:]. If we do and
	// we're not admin, re-launch via ShellExecute("runas", ...) and exit.
	// CLI subcommands like "check", "version", "update" don't need admin
	// and will run without UAC prompt.
	if CmdNeedsAdmin(os.Args[1:]) && !IsAdmin() {
		if err := ReElevate(os.Args[1:]); err != nil {
			fmt.Fprintf(os.Stderr, "failed to re-elevate: %v\n", err)
		}
		os.Exit(0)
	}

	// Inject our build version so the updater package can stamp it on the
	// User-Agent header it sends to git.okcu.io.
	updater.SetVersion(Version)

	if err := newRootCmd().Execute(); err != nil {
		// Cobra already prints the error; just exit non-zero.
		os.Exit(1)
	}
}

func newRootCmd() *cobra.Command {
	root := &cobra.Command{
		Use:           "drover",
		Short:         "Discord proxy via SOCKS5 + WinDivert",
		Version:       fmt.Sprintf("%s (commit %s, built %s)", Version, Commit, BuildDate),
		SilenceUsage:  true,
		SilenceErrors: false,
		// No subcommand and no flags = end-user double-clicked the exe.
		// First do a quick silent update check (no-op if offline or
		// already current); if an update is available we apply it and
		// re-launch ourselves. Then we open the Wails-backed GUI.
		RunE: func(cmd *cobra.Command, args []string) error {
			autoUpdateOnStartup()
			return gui.Run(Version)
		},
	}

	// Custom version template: "drover-go vX.Y.Z (commit abc1234, built 2026-05-01)".
	root.SetVersionTemplate(fmt.Sprintf("drover-go v%s (commit %s, built %s)\n", Version, Commit, BuildDate))

	root.PersistentFlags().StringVar(&configPath, "config", "", "path to TOML config file (reserved)")

	root.AddCommand(newCheckCmd())
	root.AddCommand(newUpdateCmd())
	root.AddCommand(newServiceCmd())
	root.AddCommand(newGUICmd())

	return root
}

func newGUICmd() *cobra.Command {
	return &cobra.Command{
		Use:   "gui",
		Short: "Open the Drover-Go window (same as launching the exe with no args)",
		RunE: func(cmd *cobra.Command, args []string) error {
			return gui.Run(Version)
		},
	}
}

func newCheckCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "check",
		Short: "Run the 7-step proxy diagnostic",
		RunE: func(cmd *cobra.Command, args []string) error {
			fmt.Fprintln(cmd.OutOrStdout(), "TODO: 7-step diagnostic")
			return nil
		},
	}
}

func newUpdateCmd() *cobra.Command {
	var checkOnly bool
	cmd := &cobra.Command{
		Use:   "update",
		Short: "Self-update via the Forgejo Releases API",
		RunE: func(cmd *cobra.Command, args []string) error {
			ctx := cmd.Context()
			out := cmd.OutOrStdout()

			src := updater.NewForgejoSource("git.okcu.io", "root", "drover-go", "windows-amd64.exe")
			rel, hasUpdate, err := updater.CheckForUpdate(ctx, src, Version)
			if err != nil {
				return fmt.Errorf("check for update: %w", err)
			}
			if !hasUpdate {
				fmt.Fprintln(out, "No updates available")
				return nil
			}
			fmt.Fprintf(out, "Update available: %s (current v%s)\n", rel.TagName, Version)
			if checkOnly {
				return nil
			}

			fmt.Fprintln(out, "Downloading...")
			if err := updater.ApplyUpdate(ctx, rel, func(d, t int64) {
				if t > 0 {
					fmt.Fprintf(out, "\r%d/%d bytes", d, t)
				}
			}); err != nil {
				return fmt.Errorf("apply update: %w", err)
			}
			fmt.Fprintln(out, "\nUpdate applied. Restart drover.")
			return nil
		},
	}
	cmd.Flags().BoolVar(&checkOnly, "check-only", false, "only check for an update, do not apply")
	return cmd
}

func newServiceCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "service",
		Short: "Manage the drover Windows service",
	}
	for _, name := range []string{"install", "uninstall", "start", "stop"} {
		name := name
		cmd.AddCommand(&cobra.Command{
			Use:   name,
			Short: fmt.Sprintf("%s the drover Windows service", name),
			RunE: func(cmd *cobra.Command, args []string) error {
				fmt.Fprintf(cmd.OutOrStdout(), "TODO: service %s\n", name)
				return nil
			},
		})
	}
	return cmd
}
