package cmd

import (
	"errors"
	"fmt"
	"io"
	"net/url"
	"os"
	"strings"

	"github.com/charmbracelet/x/term"
	"github.com/spf13/cobra"
	"github.com/tidbcloud/clinic-cli/internal/config"
)

type authShowResponse struct {
	Env          string `json:"env"`
	URL          string `json:"url"`
	HasAPIKey    bool   `json:"has_api_key"`
	APIKeyMasked string `json:"api_key_masked"`
}

func authCommandHandler(ctx *RuntimeContext, args []string, stdout io.Writer) error {
	command := newAuthCommand(staticRuntime(ctx), stdout)
	if err := executeCobraCommand(command, args); err != nil {
		return normalizeFamilyCommandError(err, "auth")
	}
	return nil
}

func newAuthCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	command := newCommand(commandMetadata{
		Use:   "auth",
		Short: "Manage Clinic base URLs and API keys.",
		Long:  "Manage Clinic endpoint configuration and API keys.",
		Example: `  clinic auth set-key --env prod
  clinic auth set-url --env staging --url https://staging-clinic.pingcap.com
  clinic auth show --env dev`,
	}, stdout)
	command.RunE = func(c *cobra.Command, _ []string) error {
		return c.Help()
	}
	command.Args = cobra.NoArgs
	command.AddCommand(
		newAuthSetKeyCommand(runtime, stdout),
		newAuthUnsetKeyCommand(runtime, stdout),
		newAuthSetURLCommand(runtime, stdout),
		newAuthShowCommand(runtime, stdout),
	)
	return command
}

func newAuthSetKeyCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	return newAuthSetKeyCommandWithReader(runtime, stdout, readAPIKeyFromTerminal)
}

func newAuthSetKeyCommandWithReader(runtime runtimeResolver, stdout io.Writer, readAPIKey func(io.Writer) (string, error)) *cobra.Command {
	var envName string
	command := newCommand(commandMetadata{
		Use:   "set-key",
		Short: "Set API key for an environment.",
		Long: `Set the Clinic API key for one environment.

Behavior:
  Prompts for API key interactively with terminal echo disabled (password-style input).`,
		Example: `  clinic auth set-key
  clinic auth set-key --env staging`,
	}, stdout)
	command.Args = cobra.NoArgs
	command.Flags().StringVar(&envName, "env", "", "Target environment. Defaults to selected environment.")
	command.RunE = func(_ *cobra.Command, _ []string) error {
		ctx, err := runtime()
		if err != nil {
			return err
		}
		return runAuthSetKeyForEnv(ctx, envName, stdout, readAPIKey)
	}
	return command
}

func newAuthSetURLCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	var envName string
	var rawURL string
	command := newCommand(commandMetadata{
		Use:   "set-url",
		Short: "Set Clinic base URL for an environment.",
		Long:  "Set the Clinic base URL for one environment.",
		Example: `  clinic auth set-url --url https://clinic.pingcap.com
  clinic auth set-url --env dev --url https://dev-clinic.pingcap.com`,
	}, stdout)
	command.Args = cobra.NoArgs
	command.Flags().StringVar(&envName, "env", "", "Target environment. Defaults to selected environment.")
	command.Flags().StringVar(&rawURL, "url", "", "Base URL. Must use http or https.")
	command.RunE = func(_ *cobra.Command, _ []string) error {
		ctx, err := runtime()
		if err != nil {
			return err
		}
		return runAuthSetURLForEnv(ctx, envName, rawURL, stdout)
	}
	return command
}

func newAuthShowCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	var envName string
	command := newCommand(commandMetadata{
		Use:   "show",
		Short: "Show URL and masked key status for an environment.",
		Long: `Show selected environment auth settings (masked output).

Output:
  JSON with env, URL, has_api_key, and a masked api_key value.`,
	}, stdout)
	command.Args = cobra.NoArgs
	command.Flags().StringVar(&envName, "env", "", "Target environment. Defaults to selected environment.")
	command.RunE = func(_ *cobra.Command, _ []string) error {
		ctx, err := runtime()
		if err != nil {
			return err
		}
		return runAuthShowForEnv(ctx, envName, stdout)
	}
	return command
}

func newAuthUnsetKeyCommand(runtime runtimeResolver, stdout io.Writer) *cobra.Command {
	var envName string
	command := newCommand(commandMetadata{
		Use:   "unset-key",
		Short: "Remove API key for an environment.",
		Long:  "Remove the stored Clinic API key from one environment.",
		Example: `  clinic auth unset-key
  clinic auth unset-key --env prod`,
	}, stdout)
	command.Args = cobra.NoArgs
	command.Flags().StringVar(&envName, "env", "", "Target environment. Defaults to selected environment.")
	command.RunE = func(_ *cobra.Command, _ []string) error {
		ctx, err := runtime()
		if err != nil {
			return err
		}
		return runAuthUnsetKeyForEnv(ctx, envName, stdout)
	}
	return command
}

func runAuthSetKeyWithHooks(
	ctx *RuntimeContext,
	args []string,
	stdout io.Writer,
	readAPIKey func(io.Writer) (string, error),
) error {
	if readAPIKey == nil {
		readAPIKey = readAPIKeyFromTerminal
	}

	command := newAuthSetKeyCommandWithReader(func() (*RuntimeContext, error) {
		return ctx, nil
	}, stdout, readAPIKey)
	return executeCobraCommand(command, args)
}

func runAuthSetKeyForEnv(
	ctx *RuntimeContext,
	envName string,
	stdout io.Writer,
	readAPIKey func(io.Writer) (string, error),
) error {
	if readAPIKey == nil {
		readAPIKey = readAPIKeyFromTerminal
	}

	apiKey, err := readAPIKey(authPromptWriter(ctx))
	if err != nil {
		return err
	}
	apiKey = strings.TrimSpace(apiKey)
	if apiKey == "" {
		return errors.New("api key cannot be empty")
	}

	selectedEnv, env, err := resolveAuthEnv(ctx, envName)
	if err != nil {
		return err
	}

	env.APIKey = apiKey
	ctx.Config.Environments[selectedEnv] = env
	if err := ctx.Config.Save(); err != nil {
		return err
	}

	return writeJSON(stdout, authShowResponseFor(selectedEnv, env))
}

func runAuthUnsetKeyForEnv(ctx *RuntimeContext, envName string, stdout io.Writer) error {
	selectedEnv, env, err := resolveAuthEnv(ctx, envName)
	if err != nil {
		return err
	}

	env.APIKey = ""
	ctx.Config.Environments[selectedEnv] = env
	if err := ctx.Config.Save(); err != nil {
		return err
	}

	return writeJSON(stdout, authShowResponseFor(selectedEnv, env))
}

func authPromptWriter(ctx *RuntimeContext) io.Writer {
	if ctx != nil && ctx.DiagnosticsOut != nil {
		return ctx.DiagnosticsOut
	}
	return os.Stderr
}

func readAPIKeyFromTerminal(promptOut io.Writer) (string, error) {
	if promptOut == nil {
		promptOut = os.Stderr
	}
	if _, err := fmt.Fprint(promptOut, "Enter API key: "); err != nil {
		return "", err
	}

	if os.Stdin == nil {
		_, _ = fmt.Fprintln(promptOut)
		return "", errors.New("interactive api-key input requires stdin")
	}
	stdinFD := os.Stdin.Fd()
	if !term.IsTerminal(stdinFD) {
		_, _ = fmt.Fprintln(promptOut)
		return "", errors.New("interactive api-key input requires a terminal")
	}

	secret, err := term.ReadPassword(stdinFD)
	if _, newlineErr := fmt.Fprintln(promptOut); newlineErr != nil && err == nil {
		err = newlineErr
	}
	if err != nil {
		return "", fmt.Errorf("read api key: %w", err)
	}
	return string(secret), nil
}

func runAuthSetURLForEnv(ctx *RuntimeContext, envName, rawURL string, stdout io.Writer) error {
	if rawURL == "" {
		return errors.New("missing required --url")
	}
	if err := validateHTTPURL(rawURL); err != nil {
		return err
	}

	selectedEnv, env, err := resolveAuthEnv(ctx, envName)
	if err != nil {
		return err
	}

	env.URL = rawURL
	ctx.Config.Environments[selectedEnv] = env
	if err := ctx.Config.Save(); err != nil {
		return err
	}

	return writeJSON(stdout, authShowResponseFor(selectedEnv, env))
}

func runAuthShowForEnv(ctx *RuntimeContext, envName string, stdout io.Writer) error {
	selectedEnv, env, err := resolveAuthEnv(ctx, envName)
	if err != nil {
		return err
	}

	return writeJSON(stdout, authShowResponseFor(selectedEnv, env))
}

func resolveAuthEnv(ctx *RuntimeContext, override string) (string, config.Environment, error) {
	selectedEnv := override
	if selectedEnv == "" {
		selectedEnv = ctx.SelectedEnv
	}

	env, err := ctx.Config.ResolveEnv(selectedEnv)
	if err != nil {
		return "", config.Environment{}, err
	}

	return selectedEnv, env, nil
}

func authShowResponseFor(envName string, env config.Environment) authShowResponse {
	return authShowResponse{
		Env:          envName,
		URL:          env.URL,
		HasAPIKey:    env.APIKey != "",
		APIKeyMasked: maskAPIKey(env.APIKey),
	}
}

func maskAPIKey(apiKey string) string {
	if apiKey == "" {
		return ""
	}
	return "********"
}

func validateHTTPURL(raw string) error {
	parsed, err := url.Parse(raw)
	if err != nil || parsed == nil || parsed.Host == "" {
		return fmt.Errorf("invalid url: %q", raw)
	}
	if parsed.Scheme != "http" && parsed.Scheme != "https" {
		return fmt.Errorf("url must use http or https scheme: %q", raw)
	}
	return nil
}

func rejectUnexpectedArgs(args []string) error {
	if len(args) == 0 {
		return nil
	}
	return fmt.Errorf("unexpected arguments: %v", args)
}
