package cli

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/host-uk/core/pkg/i18n"
	"github.com/host-uk/core/pkg/log"
)

// GhAuthenticated checks if the GitHub CLI is authenticated.
// Returns true if 'gh auth status' indicates a logged-in user.
func GhAuthenticated() bool {
	cmd := exec.Command("gh", "auth", "status")
	output, _ := cmd.CombinedOutput()
	authenticated := strings.Contains(string(output), "Logged in")

	if authenticated {
		LogSecurity("GitHub CLI authenticated", "user", log.Username())
	} else {
		LogSecurity("GitHub CLI not authenticated", "user", log.Username())
	}

	return authenticated
}

// ConfirmOption configures Confirm behaviour.
type ConfirmOption func(*confirmConfig)

type confirmConfig struct {
	defaultYes bool
	required   bool
	timeout    time.Duration
}

// DefaultYes sets the default response to "yes" (pressing Enter confirms).
func DefaultYes() ConfirmOption {
	return func(c *confirmConfig) {
		c.defaultYes = true
	}
}

// Required prevents empty responses; user must explicitly type y/n.
func Required() ConfirmOption {
	return func(c *confirmConfig) {
		c.required = true
	}
}

// Timeout sets a timeout after which the default response is auto-selected.
// If no default is set (not Required and not DefaultYes), defaults to "no".
//
//	Confirm("Continue?", Timeout(30*time.Second))  // Auto-no after 30s
//	Confirm("Continue?", DefaultYes(), Timeout(10*time.Second))  // Auto-yes after 10s
func Timeout(d time.Duration) ConfirmOption {
	return func(c *confirmConfig) {
		c.timeout = d
	}
}

// Confirm prompts the user for yes/no confirmation.
// Returns true if the user enters "y" or "yes" (case-insensitive).
//
// Basic usage:
//
//	if Confirm("Delete file?") { ... }
//
// With options:
//
//	if Confirm("Save changes?", DefaultYes()) { ... }
//	if Confirm("Dangerous!", Required()) { ... }
//	if Confirm("Auto-continue?", Timeout(30*time.Second)) { ... }
func Confirm(prompt string, opts ...ConfirmOption) bool {
	cfg := &confirmConfig{}
	for _, opt := range opts {
		opt(cfg)
	}

	// Build the prompt suffix
	var suffix string
	if cfg.required {
		suffix = "[y/n] "
	} else if cfg.defaultYes {
		suffix = "[Y/n] "
	} else {
		suffix = "[y/N] "
	}

	// Add timeout indicator if set
	if cfg.timeout > 0 {
		suffix = fmt.Sprintf("%s(auto in %s) ", suffix, cfg.timeout.Round(time.Second))
	}

	reader := bufio.NewReader(os.Stdin)

	for {
		fmt.Printf("%s %s", prompt, suffix)

		var response string

		if cfg.timeout > 0 {
			// Use timeout-based reading
			resultChan := make(chan string, 1)
			go func() {
				line, _ := reader.ReadString('\n')
				resultChan <- line
			}()

			select {
			case response = <-resultChan:
				response = strings.ToLower(strings.TrimSpace(response))
			case <-time.After(cfg.timeout):
				fmt.Println() // New line after timeout
				return cfg.defaultYes
			}
		} else {
			response, _ = reader.ReadString('\n')
			response = strings.ToLower(strings.TrimSpace(response))
		}

		// Handle empty response
		if response == "" {
			if cfg.required {
				continue // Ask again
			}
			return cfg.defaultYes
		}

		// Check for yes/no responses
		if response == "y" || response == "yes" {
			return true
		}
		if response == "n" || response == "no" {
			return false
		}

		// Invalid response
		if cfg.required {
			fmt.Println("Please enter 'y' or 'n'")
			continue
		}

		// Non-required: treat invalid as default
		return cfg.defaultYes
	}
}

// ConfirmAction prompts for confirmation of an action using grammar composition.
//
//	if ConfirmAction("delete", "config.yaml") { ... }
//	if ConfirmAction("save", "changes", DefaultYes()) { ... }
func ConfirmAction(verb, subject string, opts ...ConfirmOption) bool {
	question := i18n.Title(verb) + " " + subject + "?"
	return Confirm(question, opts...)
}

// ConfirmDangerousAction prompts for double confirmation of a dangerous action.
// Shows initial question, then a "Really verb subject?" confirmation.
//
//	if ConfirmDangerousAction("delete", "config.yaml") { ... }
func ConfirmDangerousAction(verb, subject string) bool {
	question := i18n.Title(verb) + " " + subject + "?"
	if !Confirm(question, Required()) {
		return false
	}

	confirm := "Really " + verb + " " + subject + "?"
	return Confirm(confirm, Required())
}

// QuestionOption configures Question behaviour.
type QuestionOption func(*questionConfig)

type questionConfig struct {
	defaultValue string
	required     bool
	validator    func(string) error
}

// WithDefault sets the default value shown in brackets.
func WithDefault(value string) QuestionOption {
	return func(c *questionConfig) {
		c.defaultValue = value
	}
}

// WithValidator adds a validation function for the response.
func WithValidator(fn func(string) error) QuestionOption {
	return func(c *questionConfig) {
		c.validator = fn
	}
}

// RequiredInput prevents empty responses.
func RequiredInput() QuestionOption {
	return func(c *questionConfig) {
		c.required = true
	}
}

// Question prompts the user for text input.
//
//	name := Question("Enter your name:")
//	name := Question("Enter your name:", WithDefault("Anonymous"))
//	name := Question("Enter your name:", RequiredInput())
func Question(prompt string, opts ...QuestionOption) string {
	cfg := &questionConfig{}
	for _, opt := range opts {
		opt(cfg)
	}

	reader := bufio.NewReader(os.Stdin)

	for {
		// Build prompt with default
		if cfg.defaultValue != "" {
			fmt.Printf("%s [%s] ", prompt, cfg.defaultValue)
		} else {
			fmt.Printf("%s ", prompt)
		}

		response, _ := reader.ReadString('\n')
		response = strings.TrimSpace(response)

		// Handle empty response
		if response == "" {
			if cfg.required {
				fmt.Println("Response required")
				continue
			}
			response = cfg.defaultValue
		}

		// Validate if validator provided
		if cfg.validator != nil {
			if err := cfg.validator(response); err != nil {
				fmt.Printf("Invalid: %v\n", err)
				continue
			}
		}

		return response
	}
}

// QuestionAction prompts for text input using grammar composition.
//
//	name := QuestionAction("rename", "old.txt")
func QuestionAction(verb, subject string, opts ...QuestionOption) string {
	question := i18n.Title(verb) + " " + subject + "?"
	return Question(question, opts...)
}

// ChooseOption configures Choose behaviour.
type ChooseOption[T any] func(*chooseConfig[T])

type chooseConfig[T any] struct {
	displayFn func(T) string
	defaultN  int  // 0-based index of default selection
	filter    bool // Enable fuzzy filtering
	multi     bool // Allow multiple selection
}

// WithDisplay sets a custom display function for items.
func WithDisplay[T any](fn func(T) string) ChooseOption[T] {
	return func(c *chooseConfig[T]) {
		c.displayFn = fn
	}
}

// WithDefaultIndex sets the default selection index (0-based).
func WithDefaultIndex[T any](idx int) ChooseOption[T] {
	return func(c *chooseConfig[T]) {
		c.defaultN = idx
	}
}

// Filter enables type-to-filter functionality.
// Users can type to narrow down the list of options.
// Note: This is a hint for interactive UIs; the basic CLI Choose
// implementation uses numbered selection which doesn't support filtering.
func Filter[T any]() ChooseOption[T] {
	return func(c *chooseConfig[T]) {
		c.filter = true
	}
}

// Multi allows multiple selections.
// Use ChooseMulti instead of Choose when this option is needed.
func Multi[T any]() ChooseOption[T] {
	return func(c *chooseConfig[T]) {
		c.multi = true
	}
}

// Display sets a custom display function for items.
// Alias for WithDisplay for shorter syntax.
//
//	Choose("Select:", items, Display(func(f File) string { return f.Name }))
func Display[T any](fn func(T) string) ChooseOption[T] {
	return WithDisplay[T](fn)
}

// Choose prompts the user to select from a list of items.
// Returns the selected item. Uses simple numbered selection for terminal compatibility.
//
//	choice := Choose("Select a file:", files)
//	choice := Choose("Select a file:", files, WithDisplay(func(f File) string { return f.Name }))
func Choose[T any](prompt string, items []T, opts ...ChooseOption[T]) T {
	var zero T
	if len(items) == 0 {
		return zero
	}

	cfg := &chooseConfig[T]{
		displayFn: func(item T) string { return fmt.Sprint(item) },
	}
	for _, opt := range opts {
		opt(cfg)
	}

	// Display options
	fmt.Println(prompt)
	for i, item := range items {
		marker := " "
		if i == cfg.defaultN {
			marker = "*"
		}
		fmt.Printf("  %s%d. %s\n", marker, i+1, cfg.displayFn(item))
	}

	reader := bufio.NewReader(os.Stdin)

	for {
		fmt.Printf("Enter number [1-%d]: ", len(items))
		response, _ := reader.ReadString('\n')
		response = strings.TrimSpace(response)

		// Empty response uses default
		if response == "" {
			return items[cfg.defaultN]
		}

		// Parse number
		var n int
		if _, err := fmt.Sscanf(response, "%d", &n); err == nil {
			if n >= 1 && n <= len(items) {
				return items[n-1]
			}
		}

		fmt.Printf("Please enter a number between 1 and %d\n", len(items))
	}
}

// ChooseAction prompts for selection using grammar composition.
//
//	file := ChooseAction("select", "file", files)
func ChooseAction[T any](verb, subject string, items []T, opts ...ChooseOption[T]) T {
	question := i18n.Title(verb) + " " + subject + ":"
	return Choose(question, items, opts...)
}

// ChooseMulti prompts the user to select multiple items from a list.
// Returns the selected items. Uses space-separated numbers or ranges.
//
//	choices := ChooseMulti("Select files:", files)
//	choices := ChooseMulti("Select files:", files, WithDisplay(func(f File) string { return f.Name }))
//
// Input format:
//   - "1 3 5" - select items 1, 3, and 5
//   - "1-3" - select items 1, 2, and 3
//   - "1 3-5" - select items 1, 3, 4, and 5
//   - "" (empty) - select none
func ChooseMulti[T any](prompt string, items []T, opts ...ChooseOption[T]) []T {
	if len(items) == 0 {
		return nil
	}

	cfg := &chooseConfig[T]{
		displayFn: func(item T) string { return fmt.Sprint(item) },
	}
	for _, opt := range opts {
		opt(cfg)
	}

	// Display options
	fmt.Println(prompt)
	for i, item := range items {
		fmt.Printf("  %d. %s\n", i+1, cfg.displayFn(item))
	}

	reader := bufio.NewReader(os.Stdin)

	for {
		fmt.Printf("Enter numbers (e.g., 1 3 5 or 1-3) or empty for none: ")
		response, _ := reader.ReadString('\n')
		response = strings.TrimSpace(response)

		// Empty response returns no selections
		if response == "" {
			return nil
		}

		// Parse the selection
		selected, err := parseMultiSelection(response, len(items))
		if err != nil {
			fmt.Printf("Invalid selection: %v\n", err)
			continue
		}

		// Build result
		result := make([]T, 0, len(selected))
		for _, idx := range selected {
			result = append(result, items[idx])
		}
		return result
	}
}

// parseMultiSelection parses a multi-selection string like "1 3 5" or "1-3 5".
// Returns 0-based indices.
func parseMultiSelection(input string, maxItems int) ([]int, error) {
	selected := make(map[int]bool)
	parts := strings.Fields(input)

	for _, part := range parts {
		// Check for range (e.g., "1-3")
		if strings.Contains(part, "-") {
			rangeParts := strings.Split(part, "-")
			if len(rangeParts) != 2 {
				return nil, fmt.Errorf("invalid range: %s", part)
			}
			var start, end int
			if _, err := fmt.Sscanf(rangeParts[0], "%d", &start); err != nil {
				return nil, fmt.Errorf("invalid range start: %s", rangeParts[0])
			}
			if _, err := fmt.Sscanf(rangeParts[1], "%d", &end); err != nil {
				return nil, fmt.Errorf("invalid range end: %s", rangeParts[1])
			}
			if start < 1 || start > maxItems || end < 1 || end > maxItems || start > end {
				return nil, fmt.Errorf("range out of bounds: %s", part)
			}
			for i := start; i <= end; i++ {
				selected[i-1] = true // Convert to 0-based
			}
		} else {
			// Single number
			var n int
			if _, err := fmt.Sscanf(part, "%d", &n); err != nil {
				return nil, fmt.Errorf("invalid number: %s", part)
			}
			if n < 1 || n > maxItems {
				return nil, fmt.Errorf("number out of range: %d", n)
			}
			selected[n-1] = true // Convert to 0-based
		}
	}

	// Convert map to sorted slice
	result := make([]int, 0, len(selected))
	for i := 0; i < maxItems; i++ {
		if selected[i] {
			result = append(result, i)
		}
	}
	return result, nil
}

// ChooseMultiAction prompts for multiple selections using grammar composition.
//
//	files := ChooseMultiAction("select", "files", files)
func ChooseMultiAction[T any](verb, subject string, items []T, opts ...ChooseOption[T]) []T {
	question := i18n.Title(verb) + " " + subject + ":"
	return ChooseMulti(question, items, opts...)
}

// GitClone clones a GitHub repository to the specified path.
// Prefers 'gh repo clone' if authenticated, falls back to SSH.
func GitClone(ctx context.Context, org, repo, path string) error {
	if GhAuthenticated() {
		httpsURL := fmt.Sprintf("https://github.com/%s/%s.git", org, repo)
		cmd := exec.CommandContext(ctx, "gh", "repo", "clone", httpsURL, path)
		output, err := cmd.CombinedOutput()
		if err == nil {
			return nil
		}
		errStr := strings.TrimSpace(string(output))
		if strings.Contains(errStr, "already exists") {
			return fmt.Errorf("%s", errStr)
		}
	}
	// Fall back to SSH clone
	cmd := exec.CommandContext(ctx, "git", "clone", fmt.Sprintf("git@github.com:%s/%s.git", org, repo), path)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%s", strings.TrimSpace(string(output)))
	}
	return nil
}
