"""CLI entry point for RAGOps Agent CE.

Follows Single Responsibility Principle - only handles CLI commands and routing.
REPL logic is delegated to dedicated REPL classes.
"""

from __future__ import annotations

import asyncio
import json
import shlex
import time
from pathlib import Path

import typer
from donkit.ragops_api_gateway_client.client import RagopsAPIGatewayClient
from pydantic_settings import BaseSettings

from donkit_ragops import __version__
from donkit_ragops.agent.agent import LLMAgent, default_tools
from donkit_ragops.agent.prompts import get_prompt
from donkit_ragops.config import load_settings
from donkit_ragops.display import print_startup_header
from donkit_ragops.enterprise.event_listener import EventListener
from donkit_ragops.llm.provider_factory import get_provider
from donkit_ragops.logging_config import setup_logging
from donkit_ragops.mcp.client import MCPClient
from donkit_ragops.mode import Mode, detect_mode
from donkit_ragops.model_selector import save_model_selection, select_model_at_startup
from donkit_ragops.repl.base import ReplContext
from donkit_ragops.repl.local_repl import LocalREPL
from donkit_ragops.schemas.agent_schemas import AgentSettings
from donkit_ragops.setup_wizard import run_setup_if_needed
from donkit_ragops.ui import get_ui
from donkit_ragops.ui.styles import StyleName, styled_text
from donkit_ragops.version_checker import check_for_updates, print_update_notification

app = typer.Typer(
    pretty_exceptions_enable=False,
)

DEFAULT_MCP_COMMANDS = ["donkit-ragops-mcp"]

# Default models for providers
DEFAULT_MODELS = {
    "openai": "gpt-4o-mini",
    "azure_openai": "gpt-4o-mini",
    "vertex": "gemini-2.5-flash",
    "ollama": "llama3.1",
    "openrouter": "openai/gpt-4o-mini",
    "donkit": "donkit-fast",
    "anthropic": "claude-3-5-sonnet-20241022",
    "mock": "gpt-4o-mini",
}


def _check_and_notify_updates() -> None:
    """Check for available updates and notify user if newer version exists."""
    try:
        version_info = check_for_updates(__version__)
        if version_info:
            print_update_notification(version_info)
    except Exception:
        # Silently ignore any errors during version check
        pass


def version_callback(value: bool) -> None:
    """Handle --version flag."""
    if value:
        ui = get_ui()
        ui.print(f"donkit-ragops {__version__}")
        raise typer.Exit()


@app.callback(invoke_without_command=True)
def main(
    ctx: typer.Context,
    version: bool = typer.Option(
        None,
        "--version",
        "-V",
        callback=version_callback,
        is_eager=True,
        help="Show version and exit",
    ),
    local: bool = typer.Option(
        False,
        "--local",
        help="Force local mode (overrides auto-detection)",
    ),
    enterprise: bool = typer.Option(
        False,
        "--enterprise",
        help="Force enterprise mode (requires authentication via 'login')",
    ),
    system: str | None = typer.Option(
        None, "--system", "-s", help="System prompt to guide the agent"
    ),
    model: str | None = typer.Option(
        None, "--model", "-m", help="LLM model to use (overrides settings)"
    ),
    provider: str | None = typer.Option(
        None, "--provider", "-p", help="LLM provider to use (overrides .env settings)"
    ),
    show_checklist: bool = typer.Option(
        True,
        "--show-checklist/--no-checklist",
        help="Render checklist panel at start and after each step",
    ),
) -> None:
    """donkit-ragops LLM-powered CLI agent for automated RAG pipeline creation.

    Start an interactive REPL session where the agent orchestrates built-in MCP servers
    to plan, chunk, read, evaluate, and load document into vector stores

    MODES:
      Local mode (default)    - Build RAG pipelines locally using Docker
      Enterprise mode         - Connect to Donkit cloud (requires 'login')

    MCP SERVERS (built-in):
      • rag-planner           - RAG configuration planning
      • chunker               - Document chunking strategies
      • read-engine           - Document parsing (PDF, Office, images)
      • vectorstore-loader    - Vector DB operations (Qdrant, Milvus, Chroma)
      • compose-manager       - Docker Compose orchestration
      • rag-query             - RAG querying
      • rag-evaluation        - RAG pipeline evaluation

    REPL COMMANDS:
      /help                   - Show available commands
      /exit                   - Exit the agent
      /clear                  - Clear conversation and screen
      /provider               - Select LLM provider
      /model                  - Select LLM model
      @/path                  - Attach files (enterprise mode only)

    EXAMPLES:
      donkit-ragops                              # Start in local mode
      donkit-ragops --enterprise                 # Start in enterprise mode
      donkit-ragops setup                        # Run setup wizard
      donkit-ragops --provider openai -m gpt-4o  # Override provider & model
      donkit-ragops status                       # Check mode & auth status
    """
    # Setup logging
    try:
        setup_logging(load_settings())
    except Exception:
        pass

    # Check for updates (async, non-blocking)
    if ctx.invoked_subcommand is None:
        _check_and_notify_updates()

    if ctx.invoked_subcommand is None:
        ui = get_ui()
        # Validate mutual exclusion
        if local and enterprise:
            ui.print_error("Cannot use --local and --enterprise together")
            raise typer.Exit(code=1)

        # Run setup wizard if needed (auto-detect only, not forced)
        if not run_setup_if_needed(force=False):
            raise typer.Exit(code=1)

        # Detect mode
        try:
            mode = detect_mode(force_local=local, force_enterprise=enterprise)
        except ValueError as e:
            ui.print_error(str(e))
            raise typer.Exit(code=1)

        # Route to appropriate REPL
        if mode == Mode.ENTERPRISE:
            _run_enterprise_mode()
        else:
            _run_local_mode(system, model, provider, show_checklist)


def _run_enterprise_mode() -> None:
    """Run enterprise mode with local LLMAgent + DonkitModel.

    MCP tools are accessed via API Gateway. All messages persisted.
    """
    from donkit_ragops.agent.agent import LLMAgent
    from donkit_ragops.agent.local_tools.tools import (
        tool_grep,
        tool_interactive_user_choice,
        tool_interactive_user_confirm,
        tool_list_directory,
        tool_read_file,
    )
    from donkit_ragops.agent.prompts import get_prompt
    from donkit_ragops.config import set_enterprise_mode
    from donkit_ragops.enterprise.auth import get_token
    from donkit_ragops.enterprise.config import load_enterprise_settings
    from donkit_ragops.enterprise.message_persister import MessagePersister
    from donkit_ragops.mcp.http_client import MCPHttpClient
    from donkit_ragops.repl.base import ReplContext
    from donkit_ragops.repl.enterprise_repl import EnterpriseREPL
    from donkit_ragops.schemas.agent_schemas import AgentSettings

    # Set global enterprise mode flag
    set_enterprise_mode(True)

    ui = get_ui()

    # Import API client and ModelFactory
    token = get_token()
    if not token:
        ui.print_error("Enterprise mode requires authentication.\nRun 'donkit-ragops login' first.")
        raise typer.Exit(code=1)

    enterprise_settings = load_enterprise_settings()

    ui.print("Mode: enterprise", StyleName.DIM)

    # Create API client for file operations and persistence
    api_client = RagopsAPIGatewayClient(
        base_url=enterprise_settings.api_url,
        api_token=token,
    )

    class DonkitSettings(BaseSettings):
        donkit_api_key: str = token
        donkit_base_url: str = enterprise_settings.api_url

    provider = get_provider(DonkitSettings(), llm_provider="donkit")
    # Create MCPHttpClient for MCP tools via API Gateway
    mcp_client = MCPHttpClient(
        url=enterprise_settings.mcp_url,
        token=token,
        timeout=600.0,
    )

    # Create minimal local tools for enterprise mode
    local_tools = [
        tool_read_file(),
        tool_list_directory(),
        tool_grep(),
        tool_interactive_user_confirm(),
        tool_interactive_user_choice(),
    ]

    # Create agent settings
    agent_settings = AgentSettings(llm_provider=provider, model=None)

    # Get system prompt for enterprise mode
    system_prompt = get_prompt(mode="enterprise", debug=False)

    # Create context first (project_id will be set during REPL init)
    context = ReplContext(
        session_started_at=time.time(),
        show_checklist=False,
        provider=provider,
        provider_name="donkit",
        model=None,
        agent=None,  # Will be set below
        agent_settings=agent_settings,
        system_prompt=system_prompt,
        api_client=api_client,
    )

    # Create LLMAgent with project_id_provider from context
    agent = LLMAgent(
        provider=provider,
        tools=local_tools,
        mcp_clients=[mcp_client],
        project_id_provider=lambda: context.project_id,
    )
    context.agent = agent

    # Create message persister (project_id will be set during REPL init)
    # Can be disabled via RAGOPS_DONKIT_PERSIST_MESSAGES=false
    message_persister = None
    if enterprise_settings.persist_messages:
        message_persister = MessagePersister(api_client=api_client, project_id="")

    # Create event listener (project_id will be set during REPL init)
    event_listener = EventListener(
        base_url=enterprise_settings.api_url,
        token=token,
        project_id="",  # Will be updated after project creation
    )

    repl = EnterpriseREPL(
        context=context,
        api_client=api_client,
        message_persister=message_persister,
        event_listener=event_listener,
        enterprise_settings=enterprise_settings,
    )
    _run_repl_with_interrupt_handling(repl)


def _run_local_mode(
    system: str | None,
    model: str | None,
    provider: str | None,
    show_checklist: bool,
) -> None:
    """Run local mode REPL."""
    from donkit_ragops import texts

    ui = get_ui()
    ui.print("Mode: local", StyleName.DIM)

    # Model selection at startup
    if provider is None:
        model_selection = select_model_at_startup()
        if model_selection is None:
            ui.print_error(texts.ERROR_MODEL_SELECTION_CANCELLED)
            raise typer.Exit(code=1)
        provider, model_from_selection = model_selection
        if model is None:
            model = model_from_selection
    else:
        save_model_selection(provider, model)

    # Ensure model is set
    if model is None:
        model = DEFAULT_MODELS.get((provider or "").lower(), "gpt-4o-mini")
        ui.print_styled(
            styled_text(
                (StyleName.WARNING, "No model selected. Using default: "),
                (StyleName.INFO, model),
            )
        )

    # Initialize provider and agent
    settings = load_settings()
    if provider:
        settings = settings.model_copy(update={"llm_provider": provider})

    try:
        prov = get_provider(settings, llm_provider=provider, model_name=model)
    except (ValueError, FileNotFoundError, json.JSONDecodeError) as e:
        from donkit_ragops import texts

        ui.print_error(texts.ERROR_PROVIDER_INIT.format(provider=provider, error=e))
        ui.print_error(texts.ERROR_CREDENTIALS_REQUIRED)
        raise typer.Exit(code=1)

    tools = default_tools()

    # Create MCP clients
    mcp_clients = []
    for cmd_str in DEFAULT_MCP_COMMANDS:
        cmd_parts = shlex.split(cmd_str)
        mcp_clients.append(MCPClient(cmd_parts[0], cmd_parts[1:]))

    # Create agent
    agent = LLMAgent(prov, tools=tools, mcp_clients=mcp_clients)

    # Create agent settings
    agent_settings = AgentSettings(llm_provider=prov, model=model)

    # Get system prompt (local mode - provider doesn't affect prompt anymore)
    system_prompt = system or get_prompt(mode="local", debug=settings.log_level == "DEBUG")

    # Print startup header
    print_startup_header(provider=provider or "initializing", model=model)

    # Create context with dependencies
    context = ReplContext(
        session_started_at=time.time(),
        show_checklist=show_checklist,
        provider=prov,
        provider_name=provider,
        model=model,
        agent=agent,
        mcp_clients=mcp_clients,
        agent_settings=agent_settings,
        system_prompt=system_prompt,
    )

    repl = LocalREPL(context)
    _run_repl_with_interrupt_handling(repl)


def _run_repl_with_interrupt_handling(repl) -> None:
    """Run REPL with proper interrupt handling."""
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    repl_task = loop.create_task(repl.run())

    try:
        while not repl_task.done():
            try:
                loop.run_until_complete(repl_task)
            except KeyboardInterrupt:
                repl_task.cancel()
    finally:
        loop.close()


@app.command()
def ping() -> None:
    """Simple health command to verify the CLI is working."""
    ui = get_ui()
    ui.print("pong")


@app.command()
def status() -> None:
    """Show current mode and authentication status."""
    from donkit_ragops.enterprise.auth import has_token

    ui = get_ui()
    mode = detect_mode()

    ui.print_styled(styled_text((StyleName.BOLD, "Mode: "), (None, mode.value)))
    ui.print_styled(styled_text((StyleName.BOLD, "Version: "), (None, __version__)))

    if mode.value == "local":
        try:
            local_settings = load_settings()
            ui.print_styled(
                styled_text(
                    (StyleName.BOLD, "Provider: "),
                    (None, local_settings.llm_provider or "not configured"),
                )
            )
            ui.print_styled(
                styled_text(
                    (StyleName.BOLD, "Model: "),
                    (None, local_settings.llm_model or "default"),
                )
            )
            ui.print_styled(
                styled_text(
                    (StyleName.BOLD, "MCP Servers: "),
                    (None, ", ".join(DEFAULT_MCP_COMMANDS)),
                )
            )
        except Exception:
            ui.print_styled(
                styled_text(
                    (StyleName.BOLD, "Provider: "),
                    (StyleName.DIM, "not configured"),
                )
            )

    if has_token():
        ui.print_styled(
            styled_text(
                (StyleName.BOLD, "Enterprise auth: "),
                (StyleName.SUCCESS, "authenticated"),
            )
        )
    else:
        ui.print_styled(
            styled_text(
                (StyleName.BOLD, "Enterprise auth: "),
                (StyleName.DIM, "not authenticated"),
            )
        )
        ui.print("Run 'donkit-ragops login' to authenticate", StyleName.DIM)


@app.command()
def setup() -> None:
    """Run setup wizard to configure the agent.

    Choose between Local mode (Docker-based) or SaaS mode (Donkit cloud).
    Local mode configuration includes LLM provider selection and credentials.
    """
    from donkit_ragops.interactive_input import interactive_select
    from donkit_ragops.setup_wizard import SaaSSetupWizard, SetupWizard

    ui = get_ui()
    ui.clear()

    # Step 0: Choose mode
    ui.print_styled(
        styled_text(
            (StyleName.BOLD, "Setup Wizard"),
        )
    )
    ui.newline()
    ui.print_styled(
        styled_text(
            (StyleName.BOLD, "Step 1: "),
            (None, "Choose deployment mode"),
        )
    )
    ui.newline()

    mode_choices = [
        "Local - Build RAG pipelines locally using Docker (free, self-hosted)",
        "SaaS - Use Donkit cloud infrastructure (managed service)",
    ]

    selected_mode = interactive_select(
        choices=mode_choices,
        title="Select deployment mode",
    )

    if selected_mode is None:
        ui.print_error("Setup cancelled")
        raise typer.Exit(code=1)

    ui.newline()

    if selected_mode == mode_choices[0]:  # Local mode
        ui.print_styled(
            styled_text(
                (None, "Selected: "),
                (StyleName.SUCCESS, "Local mode"),
            )
        )
        ui.newline()

        # Run existing local setup wizard
        env_path = Path.cwd() / ".env"
        wizard = SetupWizard(env_path)
        success = wizard.run()

        if success:
            wizard.show_success()
        else:
            ui.print_error("Setup failed or cancelled")
            raise typer.Exit(code=1)

    else:  # SaaS mode
        ui.print_styled(
            styled_text(
                (None, "Selected: "),
                (StyleName.SUCCESS, "SaaS mode"),
            )
        )
        ui.newline()

        # Run SaaS setup wizard
        wizard = SaaSSetupWizard()
        success = wizard.run()

        if not success:
            ui.print_error("Setup failed or cancelled")
            raise typer.Exit(code=1)


@app.command()
def upgrade(
    yes: bool = typer.Option(
        False,
        "--yes",
        "-y",
        help="Skip confirmation prompt",
    ),
) -> None:
    """Upgrade donkit-ragops to the latest version.

    Automatically detects installation method (pip, pipx, poetry) and
    runs the appropriate upgrade command.
    """
    from donkit_ragops.upgrade import (
        detect_install_method,
        format_upgrade_instructions,
        run_upgrade,
    )
    from donkit_ragops.version_checker import check_for_updates

    ui = get_ui()

    # Check current and latest versions
    ui.print("Checking for updates...\n", StyleName.DIM)
    version_info = check_for_updates(__version__, use_cache=False)

    if version_info is None:
        ui.print_error("Failed to check for updates. Please try again later.\n")
        raise typer.Exit(code=1)

    if not version_info.is_outdated:
        ui.print_success(f"Already on the latest version: {version_info.current}")
        raise typer.Exit()

    # Show available update
    ui.print("")
    ui.print_styled(
        styled_text(
            (StyleName.INFO, "New version available: "),
            (StyleName.SUCCESS, version_info.latest),
            (StyleName.DIM, f" (current: {version_info.current})"),
        )
    )

    # Detect installation method
    method = detect_install_method()
    ui.print_styled(
        styled_text(
            (StyleName.DIM, "Detected installation method: "),
            (StyleName.INFO, method.value),
        )
    )

    # Confirm upgrade
    if not yes:
        ui.print("")
        confirm = typer.confirm("Proceed with upgrade?")
        if not confirm:
            ui.print("Upgrade cancelled.")
            raise typer.Exit()

    # Run upgrade
    ui.print("")
    ui.print(f"Upgrading donkit-ragops to {version_info.latest}...", StyleName.INFO)

    with ui.create_spinner("Upgrading"):
        success, output = run_upgrade(method)

    if success:
        ui.print_success(f"Successfully upgraded to {version_info.latest}")
        ui.print("")
        ui.print_styled(
            styled_text(
                (StyleName.DIM, "Please restart the CLI to use the new version."),
            )
        )
    else:
        ui.print_error(f"Upgrade failed: {output}")
        ui.print("")
        ui.print("Try upgrading manually:", StyleName.WARNING)
        ui.print_styled(
            styled_text(
                (StyleName.INFO, "  " + format_upgrade_instructions(method)),
            )
        )
        raise typer.Exit(code=1)
