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

package mcp

import (
	"errors"
	"fmt"
	"io"

	"git.secluded.site/lune/internal/config"
	"git.secluded.site/lune/internal/mcp/auth"
	"git.secluded.site/lune/internal/ui"
	"github.com/charmbracelet/huh"
	"github.com/spf13/cobra"
)

var (
	errTokenMismatch = errors.New("tokens do not match")
	errTokenEmpty    = errors.New("token cannot be empty")
)

var setTokenCmd = &cobra.Command{
	Use:   "set-token",
	Short: "Set authentication token for the MCP server",
	Long: `Set a Bearer token for authenticating MCP server requests.

When a token hash is configured, SSE and HTTP transports require clients
to provide the token via the Authorization header:

  Authorization: Bearer <token>

The token is hashed using argon2id before being stored in the config file.
Only the hash is stored, not the plaintext token.

Note: stdio transport does not use authentication (local processes only).`,
	RunE: runSetToken,
}

func init() {
	Cmd.AddCommand(setTokenCmd)
}

func runSetToken(cmd *cobra.Command, _ []string) error {
	out := cmd.OutOrStdout()

	cfg, err := loadOrCreateConfig()
	if err != nil {
		return err
	}

	if cfg.MCP.TokenHash != "" {
		proceed, confirmErr := confirmTokenReplace(out)
		if confirmErr != nil {
			return confirmErr
		}

		if !proceed {
			return nil
		}
	}

	token, err := promptForMCPToken()
	if err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			return nil
		}

		return err
	}

	hash, err := auth.Hash(token)
	if err != nil {
		return fmt.Errorf("hashing token: %w", err)
	}

	cfg.MCP.TokenHash = hash

	if err := cfg.Save(); err != nil {
		return fmt.Errorf("saving config: %w", err)
	}

	fmt.Fprintln(out, ui.Success.Render("MCP authentication token configured."))
	fmt.Fprintln(out, "Restart the MCP server for changes to take effect.")

	return nil
}

func loadOrCreateConfig() (*config.Config, error) {
	cfg, err := config.Load()
	if err != nil {
		if errors.Is(err, config.ErrNotFound) {
			return &config.Config{}, nil
		}

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

	return cfg, nil
}

func confirmTokenReplace(out io.Writer) (bool, error) {
	fmt.Fprintln(out, ui.Warning.Render("An authentication token is already configured."))
	fmt.Fprintln(out, "Setting a new token will replace the existing one.")
	fmt.Fprintln(out)

	var proceed bool

	err := huh.NewConfirm().
		Title("Replace existing token?").
		Affirmative("Yes").
		Negative("No").
		Value(&proceed).
		Run()
	if err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			return false, nil
		}

		return false, err
	}

	return proceed, nil
}

func promptForMCPToken() (string, error) {
	var token, confirm string

	err := huh.NewForm(
		huh.NewGroup(
			huh.NewInput().
				Title("MCP Authentication Token").
				Description("Enter a token to protect your MCP server.").
				EchoMode(huh.EchoModePassword).
				Value(&token).
				Validate(func(input string) error {
					if input == "" {
						return errTokenEmpty
					}

					return nil
				}),
			huh.NewInput().
				Title("Confirm Token").
				Description("Re-enter the token to confirm.").
				EchoMode(huh.EchoModePassword).
				Value(&confirm).
				Validate(func(input string) error {
					if input != token {
						return errTokenMismatch
					}

					return nil
				}),
		),
	).Run()
	if err != nil {
		return "", err
	}

	return token, nil
}
