package cmd

import (
	"bufio"
	"context"
	"fmt"
	"net/url"
	"os"
	"strings"

	"gogs.dmsc.dev/arp/arp_cli/config"

	"github.com/AlecAivazis/survey/v2"
	"github.com/urfave/cli/v3"
)

// REPL represents a Read-Eval-Print Loop for interactive CLI usage
type REPL struct {
	app       *cli.Command
	prompt    string
	history   []string
	serverURL string // Session-specific server URL
	token     string // Session-specific token
}

// NewREPL creates a new REPL instance
func NewREPL(app *cli.Command) *REPL {
	return &REPL{
		app:    app,
		prompt: "arp> ",
	}
}

// Run starts the REPL loop
func (r *REPL) Run(ctx context.Context) error {
	// Load saved config for defaults
	cfg, err := config.Load()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	// Prompt for server URL
	serverURL, err := r.promptServerURL(cfg)
	if err != nil {
		return err
	}
	r.serverURL = serverURL
	r.token = cfg.Token // Use saved token as starting point

	// Update prompt to show connection info
	r.updatePrompt()

	fmt.Println("ARP CLI - Interactive Mode")
	fmt.Printf("Connected to: %s\n", r.serverURL)
	fmt.Println("Type 'help' for available commands, 'exit' or 'quit' to leave.")
	fmt.Println()

	scanner := bufio.NewScanner(os.Stdin)

	for {
		// Show prompt
		fmt.Print(r.prompt)

		// Read line
		if !scanner.Scan() {
			// EOF (Ctrl+D)
			fmt.Println()
			break
		}

		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		// Check for exit commands
		if line == "exit" || line == "quit" || line == "q" {
			fmt.Println("Goodbye!")
			break
		}

		// Parse the line into args
		args, err := parseArgs(line)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error parsing command: %v\n", err)
			continue
		}

		// Prepend the app name to match CLI expectations
		args = append([]string{"arp_cli"}, args...)

		// Inject session URL if not already specified
		args = r.injectSessionURL(args)

		// Execute the command
		if err := r.app.Run(ctx, args); err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		}

		// Store in history
		r.history = append(r.history, line)

		// Reload config to pick up any token changes from login
		r.reloadToken()
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("reading input: %w", err)
	}

	return nil
}

// promptServerURL prompts the user for a server URL
func (r *REPL) promptServerURL(cfg *config.Config) (string, error) {
	// Determine default URL
	defaultURL := cfg.ServerURL
	if defaultURL == "" {
		defaultURL = "http://localhost:8080/query"
	}

	var serverURL string
	prompt := &survey.Input{
		Message: "Server URL",
		Default: defaultURL,
		Help:    "The ARP server URL to connect to for this session",
	}
	if err := survey.AskOne(prompt, &serverURL); err != nil {
		return "", err
	}

	// Ensure URL has the /query endpoint
	serverURL = ensureQueryEndpoint(serverURL)

	return serverURL, nil
}

// updatePrompt updates the prompt to show connection info
func (r *REPL) updatePrompt() {
	// Extract a short identifier from the URL for the prompt
	shortName := r.getShortServerName()
	r.prompt = fmt.Sprintf("arp[%s]> ", shortName)
}

// getShortServerName extracts a short name from the server URL for the prompt
func (r *REPL) getShortServerName() string {
	if r.serverURL == "" {
		return "local"
	}

	parsed, err := url.Parse(r.serverURL)
	if err != nil {
		return "server"
	}

	host := parsed.Host
	// Remove port if present
	if idx := strings.Index(host, ":"); idx != -1 {
		host = host[:idx]
	}

	// Shorten common patterns
	if host == "localhost" || host == "127.0.0.1" {
		return "local"
	}

	// Truncate if too long
	if len(host) > 15 {
		host = host[:12] + "..."
	}

	return host
}

// injectSessionURL adds the --url flag to commands if not already present
func (r *REPL) injectSessionURL(args []string) []string {
	if r.serverURL == "" {
		return args
	}

	// Don't inject URL for help commands (help treats args as topics)
	if len(args) > 1 && args[1] == "help" {
		return args
	}

	// Check if --url or -u is already in args
	for i, arg := range args {
		if arg == "--url" || arg == "-u" {
			// URL already specified, don't inject
			return args
		}
		// Check for --url=value format
		if strings.HasPrefix(arg, "--url=") || strings.HasPrefix(arg, "-u=") {
			return args
		}
		// Also check if previous arg was --url/-u and this is the value
		if i > 0 && (args[i-1] == "--url" || args[i-1] == "-u") {
			return args
		}
	}

	// Find the position after the command name but before subcommands
	// For "arp_cli subcommand --flag", insert after "arp_cli"
	insertPos := 1
	if len(args) > 1 && !strings.HasPrefix(args[1], "-") {
		// There's a subcommand, insert URL flag before it
		// Actually, we want to insert after the subcommand name
		insertPos = 2
		if len(args) > 2 && !strings.HasPrefix(args[2], "-") {
			// There's a nested subcommand (e.g., workflow template list)
			insertPos = 3
			if len(args) > 3 && !strings.HasPrefix(args[3], "-") {
				insertPos = 4
			}
		}
	}

	// Insert --url flag with value
	newArgs := make([]string, 0, len(args)+2)
	newArgs = append(newArgs, args[:insertPos]...)
	newArgs = append(newArgs, "--url", r.serverURL)
	newArgs = append(newArgs, args[insertPos:]...)

	return newArgs
}

// reloadToken reloads the token from config (called after commands that might change auth)
func (r *REPL) reloadToken() {
	cfg, err := config.Load()
	if err == nil {
		r.token = cfg.Token
	}
}

// parseArgs parses a command line string into arguments
// Handles quoted strings and basic escaping
func parseArgs(line string) ([]string, error) {
	var args []string
	var current strings.Builder
	inQuote := false
	quoteChar := rune(0)

	for i, ch := range line {
		switch {
		case ch == '\\' && i+1 < len(line):
			// Handle escape sequences
			next := rune(line[i+1])
			if next == '"' || next == '\'' || next == '\\' || next == ' ' {
				current.WriteRune(next)
				// Skip the next character
				line = line[:i] + line[i+1:]
			} else {
				current.WriteRune(ch)
			}
		case (ch == '"' || ch == '\'') && !inQuote:
			inQuote = true
			quoteChar = ch
		case ch == quoteChar && inQuote:
			inQuote = false
			quoteChar = 0
		case ch == ' ' && !inQuote:
			if current.Len() > 0 {
				args = append(args, current.String())
				current.Reset()
			}
		default:
			current.WriteRune(ch)
		}
	}

	// Add the last argument
	if current.Len() > 0 {
		args = append(args, current.String())
	}

	return args, nil
}
