package cmd

import (
	"context"
	"fmt"
	"os"
	"runtime"
	"time"

	"charm.land/lipgloss/v2"
	"github.com/charmbracelet/crush/internal/format"
	"github.com/charmbracelet/crush/internal/tui/components/anim"
	"github.com/charmbracelet/crush/internal/tui/styles"
	"github.com/charmbracelet/crush/internal/update"
	"github.com/charmbracelet/crush/internal/version"
	"github.com/charmbracelet/x/term"
	"github.com/spf13/cobra"
)

var updateCmd = &cobra.Command{
	Use:   "update",
	Short: "Check for and apply updates",
	Long: `Check if a new version of Crush is available.
Use 'update apply' to download and install the latest version.`,
	Example: `
# Check if an update is available
crush update

# Apply the update if available
crush update apply

# Force re-download even if already on latest version
crush update apply --force
  `,
	RunE: func(cmd *cobra.Command, args []string) error {
		ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
		defer cancel()

		spinner := newUpdateSpinner(ctx, cancel, "Checking for updates")
		spinner.Start()

		info, err := update.Check(ctx, version.Version, update.Default)
		spinner.Stop()

		if err != nil {
			return fmt.Errorf("failed to check for updates: %w", err)
		}

		if info.IsDevelopment() {
			fmt.Fprint(os.Stderr, info.DevelopmentVersionMessage(false))
			return nil
		}

		if !info.Available() {
			fmt.Fprintf(os.Stderr, "You are already running the latest version (v%s).\n", info.Current)
			return nil
		}

		// Check install method and provide appropriate instructions.
		method := update.DetectInstallMethod()
		if !method.CanSelfUpdate() {
			fmt.Fprintf(os.Stderr, "Update available: v%s → v%s\n", info.Current, info.Latest)
			fmt.Fprintf(os.Stderr, "Crush was installed via %s. To update, run:\n", method)
			fmt.Fprintf(os.Stderr, "  %s\n", method.UpdateInstructions())
			return nil
		}

		fmt.Fprintf(os.Stderr, "Update available: v%s → v%s\n", info.Current, info.Latest)
		fmt.Fprintf(os.Stderr, "Run 'crush update apply' to install the latest version.\n")
		fmt.Fprintf(os.Stderr, "Or visit %s to download manually.\n", info.URL)

		return nil
	},
}

var updateApplyCmd = &cobra.Command{
	Use:   "apply",
	Short: "Apply the latest update",
	Long:  `Download and install the latest version of Crush.`,
	Example: `
# Apply the latest update
crush update apply

# Force re-download even if already on latest version
crush update apply --force
  `,
	RunE: func(cmd *cobra.Command, args []string) error {
		force, _ := cmd.Flags().GetBool("force")

		ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Minute)
		defer cancel()

		// Check install method first.
		method := update.DetectInstallMethod()
		if !method.CanSelfUpdate() {
			fmt.Fprintf(os.Stderr, "Crush was installed via %s.\n", method)
			fmt.Fprintf(os.Stderr, "Self-update is not supported for this installation method.\n")
			fmt.Fprintf(os.Stderr, "To update, run:\n")
			fmt.Fprintf(os.Stderr, "  %s\n", method.UpdateInstructions())
			return nil
		}

		spinner := newUpdateSpinner(ctx, cancel, "Checking for updates")
		spinner.Start()

		info, err := update.Check(ctx, version.Version, update.Default)
		if err != nil {
			spinner.Stop()
			return fmt.Errorf("failed to check for updates: %w", err)
		}

		if info.IsDevelopment() {
			spinner.Stop()
			fmt.Fprint(os.Stderr, info.DevelopmentVersionMessage(true))
			return nil
		}

		if !info.Available() && !force {
			spinner.Stop()
			fmt.Fprintf(os.Stderr, "You are already running the latest version (v%s).\n", info.Current)
			fmt.Fprintf(os.Stderr, "Use --force to re-download and reinstall.\n")
			return nil
		}

		// Find the appropriate asset for this platform.
		asset, err := update.FindAsset(info.Release.Assets)
		if err != nil {
			spinner.Stop()
			return fmt.Errorf("failed to find update for your platform: %w", err)
		}

		spinner.Stop()
		spinner = newUpdateSpinner(ctx, cancel, fmt.Sprintf("Downloading v%s", info.Latest))
		spinner.Start()

		// Download the asset.
		binaryPath, err := update.Download(ctx, asset, info.Release)
		if err != nil {
			spinner.Stop()
			return fmt.Errorf("failed to download update: %w", err)
		}
		defer os.Remove(binaryPath)

		spinner.Stop()
		spinner = newUpdateSpinner(ctx, cancel, "Installing")
		spinner.Start()

		// Apply the update.
		if err := update.Apply(binaryPath); err != nil {
			spinner.Stop()
			return fmt.Errorf("failed to apply update: %w", err)
		}

		spinner.Stop()

		if force && !info.Available() {
			fmt.Fprintf(os.Stderr, "Successfully reinstalled v%s!\n", info.Latest)
		} else {
			fmt.Fprintf(os.Stderr, "Successfully updated to v%s!\n", info.Latest)
		}
		if runtime.GOOS == "windows" {
			fmt.Fprintf(os.Stderr, "Restart Crush to use the new version.\n")
		} else {
			fmt.Fprintf(os.Stderr, "Run 'crush -v' to verify the new version.\n")
		}

		return nil
	},
}

// newUpdateSpinner creates a spinner for update operations.
func newUpdateSpinner(ctx context.Context, cancel context.CancelFunc, label string) *format.Spinner {
	t := styles.CurrentTheme()

	// Detect background color for appropriate text color.
	hasDarkBG := true
	if term.IsTerminal(os.Stderr.Fd()) {
		hasDarkBG = lipgloss.HasDarkBackground(os.Stdin, os.Stderr)
	}
	defaultFG := lipgloss.LightDark(hasDarkBG)(lipgloss.Color("#fafafa"), t.FgBase)

	return format.NewSpinner(ctx, cancel, anim.Settings{
		Size:        10,
		Label:       label,
		LabelColor:  defaultFG,
		GradColorA:  t.Primary,
		GradColorB:  t.Secondary,
		CycleColors: true,
	})
}

func init() {
	updateApplyCmd.Flags().BoolP("force", "f", false, "Force re-download even if already on latest version")
	updateCmd.AddCommand(updateApplyCmd)
}
