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

// Package config provides the interactive setup wizard for lunatask-mcp-server.
package config

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

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

	"git.secluded.site/lunatask-mcp-server/internal/config"
	"git.secluded.site/lunatask-mcp-server/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 config 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 config command for interactive setup.
//
//nolint:exhaustruct // cobra only requires a subset of fields
var Cmd = &cobra.Command{
	Use:   "config",
	Short: "Interactive setup wizard",
	Long: `Configure lunatask-mcp-server interactively.

This command will guide you through:
  - Adding areas and goals from Lunatask
  - Adding habits from Lunatask
  - Configuring server settings (host, port, transport)
  - Configuring your access token

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

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

func runConfig(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("config requires an interactive terminal."))
		_, _ = fmt.Fprintln(cmd.ErrOrStderr())
		_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "To configure non-interactively, run:")
		_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "  lunatask-mcp-server config --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{} //nolint:exhaustruct // fresh config, defaults applied later

		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 runServerStep(cfg) },
		func() wizardNav { return runToolsStep(cfg) },
		func() wizardNav { return runAreasStep(cfg) },
		func() wizardNav { return runHabitsStep(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 lunatask-mcp-server!"))
	_, _ = fmt.Fprintln(out)
	_, _ = fmt.Fprintln(out, "This wizard will help you configure the MCP server for Lunatask.")
	_, _ = fmt.Fprintln(out)
	_, _ = fmt.Fprintln(out, "Since Lunatask is end-to-end encrypted, the server can't fetch your")
	_, _ = fmt.Fprintln(out, "areas, goals, or habits automatically. You'll need to copy IDs from")
	_, _ = fmt.Fprintln(out, "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 'lunatask-mcp-server config' again anytime to modify settings.")
	_, _ = fmt.Fprintln(out)
}

func runReconfigure(cmd *cobra.Command, cfg *config.Config) error {
	_, _ = fmt.Fprintln(cmd.OutOrStdout(), ui.Bold.Render("lunatask-mcp-server configuration"))
	_, _ = fmt.Fprintln(cmd.OutOrStdout())

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

	handlers := map[string]func() error{
		"areas":  func() error { return manageAreas(cfg) },
		"habits": func() error { return manageHabits(cfg) },
		"server": func() error { return configureServer(cfg) },
		"tools":  func() error { return configureTools(cfg) },
		"token":  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 habits", "habits"),
				huh.NewOption("Server settings", "server"),
				huh.NewOption("Enabled tools", "tools"),
				huh.NewOption("Access token", "token"),
				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, "  Habits:    %d\n", len(cfg.Habits))
	_, _ = fmt.Fprintf(out, "  Tools:     %s\n", formatToolsSummary(&cfg.Tools))
	_, _ = fmt.Fprintf(out, "  Server:    %s:%d (%s)\n", cfg.Server.Host, cfg.Server.Port, cfg.Server.Transport)
	_, _ = fmt.Fprintf(out, "  Timezone:  %s\n", cfg.Timezone)
	_, _ = fmt.Fprintln(out)
}

func formatToolsSummary(tools *config.ToolsConfig) string {
	enabled := 0
	total := 5

	if tools.GetTimestamp {
		enabled++
	}

	if tools.CreateTask {
		enabled++
	}

	if tools.UpdateTask {
		enabled++
	}

	if tools.DeleteTask {
		enabled++
	}

	if tools.TrackHabitActivity {
		enabled++
	}

	return fmt.Sprintf("%d/%d enabled", enabled, total)
}

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 = `# lunatask-mcp-server configuration file
# See: https://git.secluded.site/lunatask-mcp-server for documentation

# Server settings
[server]
host = "localhost"
port = 8080
# Transport mode: "stdio" (default), "sse", or "http"
transport = "stdio"

# Timezone for date parsing (IANA format)
timezone = "UTC"

# Enable or disable individual MCP tools (all enabled by default)
[tools]
get_timestamp = true
create_task = true
update_task = true
delete_task = true
track_habit_activity = true

# 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"

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

# Access token: Set via environment variable LUNATASK_ACCESS_TOKEN
# or store in system keyring using: lunatask-mcp-server config
`

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 and habits.")
	_, _ = fmt.Fprintln(cmd.OutOrStdout())
	_, _ = fmt.Fprintln(cmd.OutOrStdout(), "To set your access token:")
	_, _ = fmt.Fprintln(cmd.OutOrStdout(), "  • Environment variable: export LUNATASK_ACCESS_TOKEN=your-token")
	_, _ = fmt.Fprintln(cmd.OutOrStdout(), "  • System keyring: lunatask-mcp-server config (interactive)")

	return nil
}
