"""Interactive CLI mode for guided setup.

This module provides an interactive TUI for users who prefer guided
configuration over command-line flags.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import questionary
from loguru import logger

from markitai.cli import ui
from markitai.cli.console import get_console
from markitai.cli.providers_detect import (
    ProviderDetectionResult,
    detect_all_providers,
    detect_first_provider,
    format_model_list,
    get_active_models_from_config,
)

# Re-export for backward compatibility
detect_all_llm_providers = detect_all_providers
detect_llm_provider = detect_first_provider


def _is_dev_mode() -> bool:
    """Check if running in the markitai project directory (dev mode).

    Mirrors the is_dev_mode() check in setup.sh.
    """
    cwd = Path.cwd()
    pyproject = cwd / "pyproject.toml"
    if pyproject.is_file() and (cwd / ".git").exists() and (cwd / "scripts").is_dir():
        try:
            return "markitai" in pyproject.read_text(encoding="utf-8")
        except OSError:
            pass
    return False


def _get_default_env_path() -> Path:
    """Return the default .env path based on dev vs installed mode.

    Dev mode:  ./.env  (project root, loaded first by main.py)
    Installed: ~/.markitai/.env  (global config dir)
    """
    if _is_dev_mode():
        return Path.cwd() / ".env"
    return Path.home() / ".markitai" / ".env"


@dataclass
class InteractiveSession:
    """State for an interactive CLI session."""

    input_path: Path | str | None = None
    input_type: str = "file"  # "file", "directory", "url"
    output_dir: Path = field(default_factory=lambda: Path("./output"))
    enable_llm: bool = False
    enable_alt: bool = False
    enable_desc: bool = False
    enable_pure: bool = False
    enable_ocr: bool = False
    enable_screenshot: bool = False
    provider_result: ProviderDetectionResult | None = None
    active_models: list[str] = field(default_factory=list)


def _ask_or_exit(question: questionary.Question) -> Any:
    """Run a questionary prompt; raise KeyboardInterrupt on Ctrl+C / None."""
    result = question.ask()
    if result is None:
        raise KeyboardInterrupt
    return result


def prompt_input_type(session: InteractiveSession) -> str:
    """Prompt user to select input type."""
    choices = [
        questionary.Choice("Single file", value="file"),
        questionary.Choice("Directory (batch)", value="directory"),
        questionary.Choice("URL", value="url"),
    ]
    result = _ask_or_exit(
        questionary.select(
            "What would you like to convert?",
            choices=choices,
            style=questionary.Style(
                [
                    ("highlighted", "bold"),
                    ("selected", "fg:cyan"),
                ]
            ),
        )
    )

    session.input_type = result
    return result


def prompt_input_path(session: InteractiveSession) -> Path | str:
    """Prompt user for input path."""
    if session.input_type == "url":
        result = _ask_or_exit(
            questionary.text(
                "Enter URL:",
                validate=lambda x: len(x) > 0 or "URL cannot be empty",
            )
        )
    elif session.input_type == "directory":
        result = _ask_or_exit(
            questionary.path(
                "Enter directory path:",
                only_directories=True,
            )
        )
    else:
        result = _ask_or_exit(
            questionary.path(
                "Enter file path:",
            )
        )

    path: Path | str = result if session.input_type == "url" else Path(result)
    session.input_path = path
    return path


def prompt_output_dir(session: InteractiveSession) -> Path:
    """Prompt user for output directory."""
    result = _ask_or_exit(
        questionary.path(
            "Enter output directory:",
            default=str(session.output_dir),
            only_directories=True,
        )
    )

    session.output_dir = Path(result)
    return session.output_dir


def prompt_enable_llm(session: InteractiveSession) -> bool:
    """Prompt user to enable LLM enhancement."""
    console = get_console()
    has_provider = False
    active: list[str] = []
    detected_label = ""

    # 1. Check config file for active models (weight > 0)
    from markitai.config import ConfigManager

    try:
        manager = ConfigManager()
        cfg = manager.load()
        raw_model_list = [m.model_dump() for m in cfg.llm.model_list]
        active = get_active_models_from_config(raw_model_list)
        if active:
            has_provider = True
            detected_label = f"Configured: {format_model_list(active)}"
    except Exception as e:
        logger.debug("[Interactive] Failed to load config models: {}", e)

    # 2. Fallback: auto-detect providers if no config models
    if not has_provider:
        all_providers = detect_all_providers()
        session.provider_result = all_providers[0] if all_providers else None
        if all_providers:
            has_provider = True
            active = [p.model for p in all_providers]
            names = [p.provider for p in all_providers]
            detected_label = f"Detected: {format_model_list(names)}"

    result = _ask_or_exit(
        questionary.confirm(
            "Enable LLM enhancement? (better formatting, metadata)",
            default=has_provider,
        )
    )

    session.enable_llm = result
    if result:
        if active:
            session.active_models = active
            console.print(f"[green]\u2713[/green] {detected_label}")
        else:
            console.print(
                "[yellow]![/yellow] No LLM provider found. "
                "Set MODEL env var or run [bold]markitai init[/bold] to configure."
            )
    return session.enable_llm


def prompt_llm_options(session: InteractiveSession) -> None:
    """Prompt user for LLM-related options."""
    if not session.enable_llm:
        return

    choices = _ask_or_exit(
        questionary.checkbox(
            "Select LLM features:",
            choices=[
                questionary.Choice(
                    "Generate alt text for images", value="alt", checked=False
                ),
                questionary.Choice(
                    "Generate image descriptions (JSON)", value="desc", checked=False
                ),
                questionary.Choice(
                    "Pure mode (text cleaning only, no metadata)",
                    value="pure",
                    checked=False,
                ),
            ],
        )
    )

    session.enable_alt = "alt" in choices
    session.enable_desc = "desc" in choices
    session.enable_pure = "pure" in choices


def prompt_extra_options(session: InteractiveSession) -> None:
    """Prompt user for non-LLM optional features (OCR, screenshots)."""
    choices = _ask_or_exit(
        questionary.checkbox(
            "Additional options:",
            choices=[
                questionary.Choice(
                    "Enable OCR for scanned documents", value="ocr", checked=False
                ),
                questionary.Choice(
                    "Take page screenshots", value="screenshot", checked=False
                ),
            ],
        )
    )

    session.enable_ocr = "ocr" in choices
    session.enable_screenshot = "screenshot" in choices


def prompt_configure_provider(session: InteractiveSession) -> bool:
    """Prompt user to configure LLM provider if none detected."""
    if session.provider_result:
        return True

    get_console().print("[yellow]![/yellow] No LLM provider detected.")

    choices = [
        questionary.Choice("Auto-detect (Claude CLI / Copilot CLI)", value="auto"),
        questionary.Choice("Enter API key manually", value="manual"),
        questionary.Choice("Use .env file", value="env"),
        questionary.Choice("Skip for now", value="skip"),
    ]

    result = _ask_or_exit(
        questionary.select(
            "How would you like to configure LLM?",
            choices=choices,
        )
    )

    if result == "skip":
        session.enable_llm = False
        return False

    if result == "manual":
        return _prompt_manual_api_key(session)

    if result == "env":
        return _prompt_env_file(session)

    # result == "auto": run auto-detection
    detected = detect_first_provider()
    if detected:
        session.provider_result = detected
        get_console().print(
            f"[green]\u2713[/green] Detected: {detected.provider} ({detected.source})"
        )
        return True

    get_console().print(
        "[red]\u2717[/red] No provider found. "
        "Install Claude CLI / Copilot CLI, or set an API key."
    )
    return False


def _prompt_manual_api_key(session: InteractiveSession) -> bool:
    """Prompt for manual API key entry.

    Saves API key to .env file and references it via env: prefix in config.
    """
    provider = _ask_or_exit(
        questionary.select(
            "Select provider:",
            choices=[
                questionary.Choice("Anthropic (Claude)", value="anthropic"),
                questionary.Choice("OpenAI", value="openai"),
                questionary.Choice("Google (Gemini)", value="gemini"),
                questionary.Choice("DeepSeek", value="deepseek"),
            ],
        )
    )

    api_key = _ask_or_exit(
        questionary.password(
            f"Enter {provider.upper()} API key:",
        )
    )

    model_map = {
        "anthropic": "anthropic/claude-sonnet-4-6",
        "openai": "openai/gpt-5.4",
        "gemini": "gemini/gemini-3.1-flash-lite-preview",
        "deepseek": "deepseek/deepseek-chat",
    }

    env_var_map = {
        "anthropic": "ANTHROPIC_API_KEY",
        "openai": "OPENAI_API_KEY",
        "gemini": "GEMINI_API_KEY",
        "deepseek": "DEEPSEEK_API_KEY",
    }

    # Save API key to .env file (project-local in dev mode, global otherwise)
    env_var = env_var_map[provider]
    env_path = _get_default_env_path()
    env_path.parent.mkdir(parents=True, exist_ok=True)
    _append_env_var(env_path, env_var, api_key)

    # Save config with env: reference (no plaintext key)
    from markitai.config import ConfigManager, LiteLLMParams, ModelConfig

    manager = ConfigManager()
    cfg = manager.load()
    cfg.llm.model_list = [
        ModelConfig(
            model_name="default",
            litellm_params=LiteLLMParams(
                model=model_map[provider], api_key=f"env:{env_var}"
            ),
        )
    ]
    manager.save()

    session.provider_result = ProviderDetectionResult(
        provider=provider,
        model=model_map[provider],
        authenticated=True,
        source="manual",
    )

    get_console().print(f"[green]✓[/green] API key saved to {env_path}")
    get_console().print(
        f"[green]✓[/green] Config uses [cyan]env:{env_var}[/cyan] reference"
    )
    return True


def _append_env_var(env_path: Path, var_name: str, value: str) -> None:
    """Append or update an environment variable in a .env file."""
    from markitai.security import atomic_write_text

    lines: list[str] = []
    found = False

    if env_path.exists():
        lines = env_path.read_text(encoding="utf-8").splitlines()
        for i, line in enumerate(lines):
            # Match both unquoted (VAR=val) and quoted (VAR="val") forms
            if line.startswith(f"{var_name}="):
                lines[i] = f'{var_name}="{value}"'
                found = True
                break

    if not found:
        lines.append(f'{var_name}="{value}"')

    atomic_write_text(env_path, "\n".join(lines) + "\n")


def _prompt_env_file(session: InteractiveSession) -> bool:
    """Prompt for .env file location."""
    global_env = str(_get_default_env_path())
    env_path = _ask_or_exit(
        questionary.path(
            "Enter .env file path:",
            default=global_env,
        )
    )

    if not Path(env_path).exists():
        get_console().print("[red]✗[/red] .env file not found")
        return False

    # Load .env file
    from dotenv import load_dotenv

    load_dotenv(env_path)

    # Re-detect provider
    session.provider_result = detect_first_provider()

    if session.provider_result:
        get_console().print(
            f"[green]✓[/green] Detected: {session.provider_result.provider}"
        )
        return True

    get_console().print("[red]✗[/red] No API key found in .env file")
    return False


def _print_loaded_paths() -> None:
    """Print detected config and .env file paths in the header."""
    from markitai.config import ConfigManager

    console = get_console()

    # Detect config file
    manager = ConfigManager()
    manager.load()
    config_path = manager.config_path
    if config_path:
        console.print(f"  [dim]Config:[/dim] {config_path}")

    # Detect .env file(s)
    env_paths = [
        Path.cwd() / ".env",
        Path.home() / ".markitai" / ".env",
    ]
    for env_p in env_paths:
        if env_p.is_file():
            console.print(f"  [dim].env:[/dim]   {env_p}")
            break

    if config_path or any(p.is_file() for p in env_paths):
        console.print()


def run_interactive() -> InteractiveSession:
    """Run the interactive CLI session.

    Returns:
        InteractiveSession with user's choices populated.
    """
    session = InteractiveSession()
    console = get_console()

    # Print header using unified UI
    console.print()
    ui.title("Markitai Interactive")
    _print_loaded_paths()
    console.print("  Answer the following questions to configure your conversion.")
    console.print()

    # 1. Input type
    prompt_input_type(session)

    # 2. Input path
    prompt_input_path(session)

    # 3. Output directory
    prompt_output_dir(session)

    # 4. LLM enablement
    prompt_enable_llm(session)

    # 5. Configure provider if needed (skip if config models or auto-detected)
    if session.enable_llm and not session.active_models and not session.provider_result:
        if not prompt_configure_provider(session):
            session.enable_llm = False

    # 6. LLM options
    if session.enable_llm:
        prompt_llm_options(session)

    # 7. Extra options (OCR, screenshots — independent of LLM)
    prompt_extra_options(session)

    # Print summary
    _print_summary(session)

    return session


def _print_summary(session: InteractiveSession) -> None:
    """Print a summary of the session configuration."""
    console = get_console()
    console.print()
    console.print("[bold]Configuration Summary:[/bold]")
    console.print(f"  Input: {session.input_path} ({session.input_type})")
    console.print(f"  Output: {session.output_dir}")
    console.print(f"  LLM: {'enabled' if session.enable_llm else 'disabled'}")

    if session.enable_llm:
        if session.active_models:
            console.print(f"  Models: {format_model_list(session.active_models)}")
        elif session.provider_result:
            console.print(f"  Provider: {session.provider_result.provider}")
        if session.enable_pure:
            console.print("  Pure mode: yes")
        console.print(f"  Alt text: {'yes' if session.enable_alt else 'no'}")
        console.print(f"  Descriptions: {'yes' if session.enable_desc else 'no'}")
    if session.enable_ocr:
        console.print("  OCR: yes")
    if session.enable_screenshot:
        console.print("  Screenshots: yes")
    console.print()


def session_to_cli_args(session: InteractiveSession) -> list[str]:
    """Convert InteractiveSession to CLI arguments.

    This allows the interactive session to be executed by the main CLI.
    """
    args: list[str] = []

    if session.input_path:
        args.append(str(session.input_path))

    args.extend(["-o", str(session.output_dir)])

    if session.enable_llm:
        args.append("--llm")
        if session.enable_pure:
            args.append("--pure")
        if session.enable_alt:
            args.append("--alt")
        if session.enable_desc:
            args.append("--desc")
    else:
        args.append("--no-llm")

    if session.enable_ocr:
        args.append("--ocr")
    if session.enable_screenshot:
        args.append("--screenshot")

    return args
