"""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 = []

# Default models for providers
DEFAULT_MODELS = {
    "openai": "gpt-5.2",
    "azure_openai": "gpt-5.2",
    "vertex": "gemini-2.5-flash",
    "ollama": "llama3.1",
    "openrouter": "openai/gpt-5.2",
    "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 uses cloud tools
    to plan, chunk, read, evaluate, and load documents into vector stores

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

    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 and provider != "donkit":
        model = DEFAULT_MODELS.get((provider or "").lower(), "gpt-5.2")
        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"),
                )
            )
        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)
