package main

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"gokin/internal/app"
	"gokin/internal/config"
	"gokin/internal/logging"
	"gokin/internal/setup"

	"github.com/spf13/cobra"
)

var (
	// version is the dev-time fallback. Release builds inject the real value
	// via `-X main.version=$(git describe --tags)` — see .github/workflows/release.yml.
	// Bump this when merging a sprint worth of changes so `go build` without
	// ldflags still shows something sensible in /version.
	version  = "0.92.0"
	cfgFile  string
	model    string
	provider string
	runSetup bool
	resume   bool
	headless bool
	prompt   string
)

func main() {
	rootCmd := &cobra.Command{
		Use:   "gokin",
		Short: "AI-powered CLI assistant for code",
		Long: `Gokin is a CLI tool for AI-assisted coding. Supports Kimi
(default), GLM, MiniMax, DeepSeek, and Ollama. It provides an
interactive chat interface with tools for reading, writing, and
editing files, running commands, and orchestrating multi-agent
workflows — with zero proxies between you and the provider you
choose.`,
		RunE: runApp,
	}

	// Global flags
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/gokin/config.yaml)")
	rootCmd.PersistentFlags().StringVar(&model, "model", "", "model to use (default depends on provider)")
	rootCmd.PersistentFlags().StringVar(&provider, "provider", "", "provider to use for this run (glm, minimax, kimi, deepseek, ollama)")
	rootCmd.PersistentFlags().BoolVar(&runSetup, "setup", false, "run the setup wizard")
	rootCmd.PersistentFlags().BoolVar(&resume, "resume", false, "resume the last session")
	rootCmd.PersistentFlags().BoolVar(&headless, "headless", false, "run one prompt without the interactive TUI")
	rootCmd.PersistentFlags().StringVar(&prompt, "prompt", "", "prompt to run in headless mode")

	// Version command
	rootCmd.AddCommand(&cobra.Command{
		Use:   "version",
		Short: "Print the version number",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("gokin version %s\n", version)
		},
	})

	// Update command
	rootCmd.AddCommand(newUpdateCmd())
	rootCmd.AddCommand(newEvalCmd())

	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}

func runApp(cmd *cobra.Command, args []string) error {
	if headless && strings.TrimSpace(prompt) == "" {
		return fmt.Errorf("--prompt is required when --headless is set")
	}

	// Run setup wizard if requested
	if runSetup {
		if err := setup.RunSetupWizard(); err != nil {
			return err
		}
		// Continue to start the app after setup
	}

	// Load configuration
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	// Set version from runtime
	cfg.Version = version

	if err := applyRuntimeOverrides(cfg, provider, model); err != nil {
		return err
	}

	// Validate configuration - if no API key, run setup wizard automatically
	if err := cfg.Validate(); err != nil {
		if errors.Is(err, config.ErrMissingAuth) {
			if headless {
				return err
			}
			// No API key configured - run setup wizard
			if err := setup.RunSetupWizard(); err != nil {
				return err
			}
			// Reload config after setup
			cfg, err = config.Load()
			if err != nil {
				return fmt.Errorf("failed to load config: %w", err)
			}
			// Re-validate
			if err := cfg.Validate(); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	// Get working directory
	workDir, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("failed to get working directory: %w", err)
	}

	// Headless runs cannot answer Builder.checkAllowedDirs' first-run prompt.
	// Allow the current workspace in memory so evals and scripts never block
	// waiting on stdin before the agent starts.
	if headless && len(cfg.Tools.AllowedDirs) == 0 {
		cfg.Tools.AllowedDirs = append(cfg.Tools.AllowedDirs, workDir)
	}

	// Create the application
	application, err := app.New(cfg, workDir)
	if err != nil {
		return fmt.Errorf("failed to create application: %w", err)
	}

	if headless {
		return application.RunHeadless(cmd.Context(), prompt)
	}

	// Attempt to resume session if requested
	if resume {
		if err := application.ResumeLastSession(); err != nil {
			// Don't fail completely, just warn
			fmt.Printf("Warning: failed to resume session: %v\nStarting new session instead.\n", err)
			// Small pause to let user read the warning
			// (not ideal but better than complex UI logic here)
		} else {
			fmt.Println("Resumed previous session.")
		}
	}

	// Check for updates on startup (non-blocking notification). Wrapped in
	// defer-recover so a panic inside the update path (network library bug,
	// nil-deref in update.NewUpdater, etc.) doesn't crash gokin before the
	// TUI even starts. v0.78.1 wrapped the equivalent app/ goroutines —
	// this is the cmd/ counterpart.
	go func() {
		defer func() {
			if r := recover(); r != nil {
				logging.Error("update-check goroutine panicked", "panic", r)
			}
		}()
		CheckForUpdateOnStartup(cfg, application)
	}()

	fmt.Println("\nStarting Gokin...")
	return application.Run()
}

func applyRuntimeOverrides(cfg *config.Config, providerOverride, modelOverride string) error {
	if cfg == nil {
		return fmt.Errorf("config is nil")
	}

	providerOverride = strings.ToLower(strings.TrimSpace(providerOverride))
	modelOverride = strings.TrimSpace(modelOverride)

	if providerOverride != "" {
		p := config.GetProvider(providerOverride)
		if p == nil {
			return fmt.Errorf("unknown provider %q (supported: %s)", providerOverride, strings.Join(config.ProviderNames(), ", "))
		}
		cfg.API.ActiveProvider = providerOverride
		cfg.API.Backend = providerOverride
		cfg.Model.Provider = providerOverride
		if modelOverride == "" && p.DefaultModel != "" {
			cfg.Model.Name = p.DefaultModel
		}
	}

	if modelOverride != "" {
		cfg.Model.Name = modelOverride
		if providerOverride == "" {
			if detected := config.DetectKnownProviderFromModel(modelOverride); detected != "" {
				cfg.Model.Provider = detected
				cfg.API.ActiveProvider = detected
				cfg.API.Backend = detected
			}
		}
	}

	return nil
}
