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

package config

import (
	"context"
	"errors"
	"fmt"
	"io"
	"time"

	"git.secluded.site/go-lunatask"
	"github.com/charmbracelet/huh"
	"github.com/spf13/cobra"

	"git.secluded.site/lunatask-mcp-server/internal/client"
	"git.secluded.site/lunatask-mcp-server/internal/ui"
)

const tokenValidationTimeout = 10 * time.Second

// handleKeyringError prints keyring error messages and returns a wrapped error.
func handleKeyringError(out io.Writer, err error) error {
	_, _ = fmt.Fprintln(out, ui.Error.Render("Failed to access system keyring: "+err.Error()))
	_, _ = fmt.Fprintln(out, "Please resolve the keyring issue and try again.")
	_, _ = fmt.Fprintln(out)
	_, _ = fmt.Fprintln(out, "Alternative: set LUNATASK_ACCESS_TOKEN environment variable.")

	return fmt.Errorf("keyring access failed: %w", err)
}

//nolint:nestif // wizard flow with multiple paths
func configureAccessToken(cmd *cobra.Command) error {
	out := cmd.OutOrStdout()

	// Check for environment variable first
	if client.HasEnvToken() {
		_, _ = fmt.Fprintln(out, ui.Success.Render("Access token found in LUNATASK_ACCESS_TOKEN environment variable."))

		token, _, _ := client.GetToken()
		if err := validateWithSpinner(token); err != nil {
			_, _ = fmt.Fprintln(out, ui.Warning.Render("Validation failed: "+err.Error()))
		} else {
			_, _ = fmt.Fprintln(out, ui.Success.Render("✓ Authentication successful"))
		}

		var action string

		err := huh.NewSelect[string]().
			Title("Environment variable is set").
			Description("The env var takes priority over keyring storage.").
			Options(
				huh.NewOption("Keep using environment variable", "keep"),
				huh.NewOption("Also store in keyring (for when env not set)", "store"),
			).
			Value(&action).
			Run()
		if err != nil {
			if errors.Is(err, huh.ErrUserAborted) {
				return errQuit
			}

			return err
		}

		if action == "keep" {
			return nil
		}

		return promptForToken(out)
	}

	hasToken, keyringErr := client.HasKeyringToken()
	if keyringErr != nil {
		return handleKeyringError(out, keyringErr)
	}

	if hasToken {
		shouldPrompt, err := handleExistingToken(out)
		if err != nil {
			return err
		}

		if !shouldPrompt {
			return nil
		}
	}

	return promptForToken(out)
}

// ensureAccessToken checks for a token and prompts for one if missing or invalid.
// Used at the start of reconfigure mode.
func ensureAccessToken(cmd *cobra.Command) error {
	out := cmd.OutOrStdout()

	// Check environment variable first
	if client.HasEnvToken() {
		return nil
	}

	hasToken, keyringErr := client.HasKeyringToken()
	if keyringErr != nil {
		return handleKeyringError(out, keyringErr)
	}

	if !hasToken {
		_, _ = fmt.Fprintln(out, ui.Warning.Render("No access token found."))
		_, _ = fmt.Fprintln(out, "Set LUNATASK_ACCESS_TOKEN or store in system keyring.")
		_, _ = fmt.Fprintln(out)

		return promptForToken(out)
	}

	existingToken, _, err := client.GetToken()
	if err != nil {
		return fmt.Errorf("reading access token: %w", err)
	}

	if err := validateWithSpinner(existingToken); err != nil {
		_, _ = fmt.Fprintln(out, ui.Warning.Render("Existing access token failed validation: "+err.Error()))

		var replace bool

		confirmErr := huh.NewConfirm().
			Title("Would you like to provide a new access token?").
			Affirmative("Yes").
			Negative("No").
			Value(&replace).
			Run()
		if confirmErr != nil {
			if errors.Is(confirmErr, huh.ErrUserAborted) {
				return errQuit
			}

			return confirmErr
		}

		if replace {
			return promptForToken(out)
		}
	}

	return nil
}

func handleExistingToken(out io.Writer) (bool, error) {
	existingToken, _, err := client.GetToken()
	if err != nil {
		return false, fmt.Errorf("reading access token: %w", err)
	}

	_, _ = fmt.Fprintln(out, "Access token found in system keyring.")

	if err := validateWithSpinner(existingToken); err != nil {
		_, _ = fmt.Fprintln(out, ui.Warning.Render("Validation failed: "+err.Error()))
	} else {
		_, _ = fmt.Fprintln(out, ui.Success.Render("✓ Authentication successful"))
	}

	var action string

	err = huh.NewSelect[string]().
		Title("Access token is already configured").
		Options(
			huh.NewOption("Keep existing token", "keep"),
			huh.NewOption("Replace with new token", "replace"),
			huh.NewOption("Delete from keyring", "delete"),
		).
		Value(&action).
		Run()
	if err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			return false, errQuit
		}

		return false, err
	}

	switch action {
	case "keep":
		return false, nil
	case "delete":
		if err := client.DeleteKeyringToken(); err != nil {
			return false, fmt.Errorf("deleting access token: %w", err)
		}

		_, _ = fmt.Fprintln(out, ui.Success.Render("Access token removed from keyring."))

		return false, nil
	default:
		return true, nil
	}
}

func promptForToken(out io.Writer) error {
	_, _ = fmt.Fprintln(out)
	_, _ = fmt.Fprintln(out, "You can get your access token from:")
	_, _ = fmt.Fprintln(out, "  Lunatask → Settings → Access Tokens")
	_, _ = fmt.Fprintln(out)

	var token string

	for {
		err := huh.NewInput().
			Title("Lunatask Access Token").
			Description("Paste your access token (it will be stored securely in your system keyring).").
			EchoMode(huh.EchoModePassword).
			Value(&token).
			Validate(func(s string) error {
				if s == "" {
					return errTokenRequired
				}

				return nil
			}).
			Run()
		if err != nil {
			if errors.Is(err, huh.ErrUserAborted) {
				return errQuit
			}

			return err
		}

		if err := validateWithSpinner(token); err != nil {
			_, _ = fmt.Fprintln(out, ui.Error.Render("✗ Authentication failed: "+err.Error()))
			_, _ = fmt.Fprintln(out, "Please check your access token and try again.")
			_, _ = fmt.Fprintln(out)

			token = ""

			continue
		}

		_, _ = fmt.Fprintln(out, ui.Success.Render("✓ Authentication successful"))

		break
	}

	if err := client.SetKeyringToken(token); err != nil {
		return fmt.Errorf("saving access token to keyring: %w", err)
	}

	_, _ = fmt.Fprintln(out, ui.Success.Render("Access token saved to system keyring."))

	return nil
}

func validateWithSpinner(token string) error {
	return ui.SpinVoid("Verifying access token…", func() error {
		return validateTokenWithPing(token)
	})
}

func validateTokenWithPing(token string) error {
	c := lunatask.NewClient(token, lunatask.UserAgent("lunatask-mcp-server/config"))

	ctx, cancel := context.WithTimeout(context.Background(), tokenValidationTimeout)
	defer cancel()

	_, err := c.Ping(ctx)

	return err
}
