// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

// Package init provides the interactive setup wizard for lune.
package init

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/charmbracelet/huh"
	"github.com/spf13/cobra"

	"git.secluded.site/lune/internal/config"
	"git.secluded.site/lune/internal/ui"
)

// errQuit signals that the user wants to exit the wizard entirely.
var errQuit = errors.New("user quit")

// errReset signals that the user reset configuration and should run fresh setup.
var errReset = errors.New("configuration reset")

// errNonInteractive signals that init was run without a terminal.
var errNonInteractive = errors.New("non-interactive terminal")

// errConfigExists signals that a config file already exists.
var errConfigExists = errors.New("config already exists")

// wizardNav represents navigation direction in the wizard.
type wizardNav int

const (
	navNext wizardNav = iota
	navBack
	navQuit
)

// Cmd is the init command for interactive setup.
var Cmd = &cobra.Command{
	Use:   "init",
	Short: "Interactive setup wizard",
	Long: `Configure lune interactively.

This command will guide you through:
  - Adding areas, goals, notebooks, and habits from Lunatask
  - Setting default area and notebook
  - Configuring and verifying your access token

Use --generate-config to create an example config file for manual editing
when running non-interactively.`,
	RunE: runInit,
}

func init() {
	Cmd.Flags().Bool("generate-config", false, "generate example config for manual editing")
}

func runInit(cmd *cobra.Command, _ []string) error {
	generateConfig, _ := cmd.Flags().GetBool("generate-config")
	if generateConfig {
		return runGenerateConfig(cmd)
	}

	if !ui.IsInteractive() {
		fmt.Fprintln(cmd.ErrOrStderr(), ui.Error.Render("lune init requires an interactive terminal."))
		fmt.Fprintln(cmd.ErrOrStderr())
		fmt.Fprintln(cmd.ErrOrStderr(), "To configure lune non-interactively, run:")
		fmt.Fprintln(cmd.ErrOrStderr(), "  lune init --generate-config")
		fmt.Fprintln(cmd.ErrOrStderr())
		fmt.Fprintln(cmd.ErrOrStderr(), "Then edit the generated config file manually.")

		return errNonInteractive
	}

	cfg, err := config.Load()
	if errors.Is(err, config.ErrNotFound) {
		cfg = &config.Config{}

		err = runFreshSetup(cmd, cfg)
		if errors.Is(err, errQuit) {
			fmt.Fprintln(cmd.OutOrStdout(), ui.Warning.Render("Setup cancelled."))

			return nil
		}

		return err
	}

	if err != nil {
		return fmt.Errorf("loading config: %w", err)
	}

	err = runReconfigure(cmd, cfg)
	if errors.Is(err, errQuit) {
		return nil
	}

	if errors.Is(err, errReset) {
		return runFreshSetup(cmd, cfg)
	}

	return err
}

// wizardStep is a function that runs a wizard step and returns navigation direction.
type wizardStep func() wizardNav

func runFreshSetup(cmd *cobra.Command, cfg *config.Config) error {
	printWelcome(cmd)

	steps := []wizardStep{
		func() wizardNav { return runUIPrefsStep(cfg) },
		func() wizardNav { return runAreasStep(cfg) },
		func() wizardNav { return runNotebooksStep(cfg) },
		func() wizardNav { return runHabitsStep(cfg) },
		func() wizardNav { return runDefaultsStep(cfg) },
		func() wizardNav { return runAccessTokenStep(cmd) },
	}

	step := 0
	for step < len(steps) {
		nav := steps[step]()

		switch nav {
		case navNext:
			step++
		case navBack:
			if step > 0 {
				step--
			}
		case navQuit:
			return errQuit
		}
	}

	return saveWithSummary(cmd, cfg)
}

func printWelcome(cmd *cobra.Command) {
	out := cmd.OutOrStdout()
	fmt.Fprintln(out, ui.Bold.Render("Welcome to lune!"))
	fmt.Fprintln(out)
	fmt.Fprintln(out, "This wizard will help you configure lune for use with Lunatask.")
	fmt.Fprintln(out)
	fmt.Fprintln(out, "Since Lunatask is end-to-end encrypted, lune can't fetch your")
	fmt.Fprintln(out, "areas, goals, notebooks, or habits automatically. You'll need to")
	fmt.Fprintln(out, "copy IDs from the Lunatask app.")
	fmt.Fprintln(out)
	fmt.Fprintln(out, ui.Bold.Render("Where to find IDs:"))
	fmt.Fprintln(out, "  Open any item's settings modal → click 'Copy [Item] ID' (bottom left)")
	fmt.Fprintln(out)
	fmt.Fprintln(out, "You can run 'lune init' again anytime to add or modify config.")
	fmt.Fprintln(out)
}

func runReconfigure(cmd *cobra.Command, cfg *config.Config) error {
	fmt.Fprintln(cmd.OutOrStdout(), ui.Bold.Render("lune configuration"))
	fmt.Fprintln(cmd.OutOrStdout())

	if err := ensureAccessToken(cmd); err != nil {
		return err
	}

	handlers := map[string]func() error{
		"areas":     func() error { return manageAreas(cfg) },
		"notebooks": func() error { return manageNotebooks(cfg) },
		"habits":    func() error { return manageHabits(cfg) },
		"defaults":  func() error { return configureDefaults(cfg) },
		"ui":        func() error { return configureUIPrefs(cfg) },
		"apikey":    func() error { return configureAccessToken(cmd) },
		"reset":     func() error { return resetConfig(cmd, cfg) },
	}

	for {
		var choice string

		err := huh.NewSelect[string]().
			Title("What would you like to configure?").
			Options(
				huh.NewOption("Manage areas & goals", "areas"),
				huh.NewOption("Manage notebooks", "notebooks"),
				huh.NewOption("Manage habits", "habits"),
				huh.NewOption("Set defaults", "defaults"),
				huh.NewOption("UI preferences", "ui"),
				huh.NewOption("Access token", "apikey"),
				huh.NewOption("Reset all configuration", "reset"),
				huh.NewOption("Done", choiceDone),
			).
			Value(&choice).
			Run()
		if err != nil {
			if errors.Is(err, huh.ErrUserAborted) {
				return errQuit
			}

			return err
		}

		if choice == choiceDone {
			return saveWithSummary(cmd, cfg)
		}

		if handler, ok := handlers[choice]; ok {
			if err := handler(); err != nil {
				return err
			}
		}
	}
}

func printConfigSummary(out io.Writer, cfg *config.Config) {
	goalCount := 0
	for _, area := range cfg.Areas {
		goalCount += len(area.Goals)
	}

	fmt.Fprintln(out)
	fmt.Fprintln(out, ui.Bold.Render("Configuration summary:"))
	fmt.Fprintf(out, "  Areas:     %d (%d goals)\n", len(cfg.Areas), goalCount)
	fmt.Fprintf(out, "  Notebooks: %d\n", len(cfg.Notebooks))
	fmt.Fprintf(out, "  Habits:    %d\n", len(cfg.Habits))

	if cfg.Defaults.Area != "" {
		fmt.Fprintf(out, "  Default area: %s\n", cfg.Defaults.Area)
	}

	if cfg.Defaults.Notebook != "" {
		fmt.Fprintf(out, "  Default notebook: %s\n", cfg.Defaults.Notebook)
	}

	color := cfg.UI.Color
	if color == "" {
		color = colorAuto
	}

	fmt.Fprintf(out, "  Color: %s\n", color)
	fmt.Fprintln(out)
}

func saveWithSummary(cmd *cobra.Command, cfg *config.Config) error {
	path, err := config.Path()
	if err != nil {
		return err
	}

	out := cmd.OutOrStdout()
	printConfigSummary(out, cfg)

	var save bool

	err = huh.NewConfirm().
		Title("Save configuration?").
		Affirmative("Save").
		Negative("Discard").
		Value(&save).
		Run()
	if err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			fmt.Fprintln(out, ui.Warning.Render("Setup cancelled; configuration was not saved."))

			return errQuit
		}

		return err
	}

	if save {
		if err := cfg.Save(); err != nil {
			return err
		}

		fmt.Fprintln(out, ui.Success.Render("Config saved to "+path))
	} else {
		fmt.Fprintln(out, ui.Warning.Render("Changes discarded."))
	}

	return nil
}

// exampleConfig is a commented TOML config for manual editing.
const exampleConfig = `# lune configuration file
# See: https://git.secluded.site/lune for documentation

# UI preferences
[ui]
# Color output: "auto" (default), "always", or "never"
color = "auto"

# Default selections for commands
[defaults]
# Default area key (must match an area defined below)
# area = "work"

# Default notebook key (must match a notebook defined below)
# notebook = "journal"

# Areas of life from Lunatask
# Find IDs in Lunatask: Open area settings → "Copy Area ID" (bottom left)
#
# [[areas]]
# id = "00000000-0000-0000-0000-000000000000"
# name = "Work"
# key = "work"
#
#   # Goals within this area
#   [[areas.goals]]
#   id = "00000000-0000-0000-0000-000000000001"
#   name = "Q1 Project"
#   key = "q1-project"

# Notebooks for notes
# Find IDs in Lunatask: Open notebook settings → "Copy Notebook ID"
#
# [[notebooks]]
# id = "00000000-0000-0000-0000-000000000000"
# name = "Journal"
# key = "journal"

# Habits to track
# Find IDs in Lunatask: Open habit settings → "Copy Habit ID"
#
# [[habits]]
# id = "00000000-0000-0000-0000-000000000000"
# name = "Exercise"
# key = "exercise"
`

func runGenerateConfig(cmd *cobra.Command) error {
	cfgPath, err := config.Path()
	if err != nil {
		return err
	}

	if _, err := os.Stat(cfgPath); err == nil {
		fmt.Fprintln(cmd.ErrOrStderr(), ui.Error.Render("Config already exists: "+cfgPath))
		fmt.Fprintln(cmd.ErrOrStderr(), "Remove or rename it first, or edit it directly.")

		return errConfigExists
	}

	dir := filepath.Dir(cfgPath)
	if err := os.MkdirAll(dir, 0o700); err != nil {
		return fmt.Errorf("creating config directory: %w", err)
	}

	if err := os.WriteFile(cfgPath, []byte(exampleConfig), 0o600); err != nil {
		return fmt.Errorf("writing config: %w", err)
	}

	fmt.Fprintln(cmd.OutOrStdout(), ui.Success.Render("Generated example config: "+cfgPath))
	fmt.Fprintln(cmd.OutOrStdout())
	fmt.Fprintln(cmd.OutOrStdout(), "Edit this file to add your Lunatask areas, notebooks, and habits.")
	fmt.Fprintln(cmd.OutOrStdout(), "Then configure your access token with: lune init --generate-config")
	fmt.Fprintln(cmd.OutOrStdout())
	fmt.Fprintln(cmd.OutOrStdout(), "To set your access token non-interactively, use your system keyring.")
	fmt.Fprintln(cmd.OutOrStdout(), "The service is 'lune' and the key is 'access_token'.")

	return nil
}
