"""Interactive setup wizard for Ogham MCP.

Walks users through database configuration, embedding provider selection,
schema migration, and MCP client configuration.

Supports: Claude Desktop, Claude Code, Cursor, VS Code (Copilot), Codex CLI, Kiro, OpenCode.
Platforms: macOS, Linux, Windows.
Execution modes: uvx (default), Docker (GHCR image).
"""

import json
import os
import platform
import shutil
from pathlib import Path

from rich.console import Console
from rich.panel import Panel
from rich.prompt import Confirm, Prompt

console = Console()

GHCR_IMAGE = "ghcr.io/ogham-mcp/ogham-mcp:latest"


# ---------------------------------------------------------------------------
# Client detection
# ---------------------------------------------------------------------------


def _client_configs() -> list[dict]:
    """Return all known MCP client config locations for the current platform."""
    system = platform.system()
    home = Path.home()

    if system == "Darwin":
        appdata = home / "Library" / "Application Support"
    elif system == "Windows":
        appdata = Path(os.environ.get("APPDATA", home / "AppData" / "Roaming"))
    else:
        appdata = home / ".config"

    clients = [
        {
            "name": "Claude Desktop",
            "path": {
                "Darwin": appdata / "Claude" / "claude_desktop_config.json",
                "Linux": home / ".config" / "Claude" / "claude_desktop_config.json",
                "Windows": appdata / "Claude" / "claude_desktop_config.json",
            }.get(system),
            "format": "claude_desktop",
        },
        {
            "name": "Project .mcp.json (Claude Code + Cursor)",
            "path": Path.cwd() / ".mcp.json",
            "always_show": True,
            "format": "mcp_json",
        },
        {
            "name": "Claude Code (global)",
            "path": home / ".claude" / ".mcp.json",
            "detect": home / ".claude",
            "format": "mcp_json",
        },
        {
            "name": "Cursor (global)",
            "path": home / ".cursor" / "mcp.json",
            "detect": home / ".cursor",
            "format": "mcp_json",
        },
        {
            "name": "VS Code (Copilot)",
            "path": Path.cwd() / ".vscode" / "mcp.json",
            "detect": Path.cwd() / ".vscode",
            "format": "vscode",
        },
        {
            "name": "Codex CLI",
            "path": home / ".codex" / "config.toml",
            "detect_cmd": "codex",
            "format": "codex_toml",
        },
        {
            "name": "Kiro",
            "path": home / ".kiro" / "settings" / "mcp.json",
            "detect": home / ".kiro",
            "format": "mcp_json",
        },
        {
            "name": "OpenCode",
            "path": home / ".config" / "opencode" / "opencode.json",
            "detect": home / ".config" / "opencode",
            "format": "opencode",
        },
    ]

    return [c for c in clients if c.get("path")]


def _detect_clients() -> list[dict]:
    """Find installed MCP clients."""
    detected = []
    for client in _client_configs():
        path = client["path"]
        # Check if the client directory or config exists
        detect_dir = client.get("detect")
        detect_cmd = client.get("detect_cmd")

        if client.get("always_show"):
            detected.append(client)
        elif detect_dir and detect_dir.exists():
            detected.append(client)
        elif detect_cmd and shutil.which(detect_cmd):
            detected.append(client)
        elif path.exists():
            detected.append(client)

    return detected


# ---------------------------------------------------------------------------
# Database prompts
# ---------------------------------------------------------------------------


def _prompt_database() -> dict:
    """Prompt for database backend and connection details."""
    console.print("\n[bold]2. Where should Ogham store your memories?[/bold]")
    console.print("   Both options use PostgreSQL with pgvector. Both have free tiers.\n")
    console.print("   [bold]1)[/bold] supabase  -- hosted Postgres (supabase.com)")
    console.print(
        "   [bold]2)[/bold] postgres  -- Neon, self-hosted, or any Postgres with pgvector\n"
    )

    choice = Prompt.ask(
        "   Choose",
        choices=["1", "2", "supabase", "postgres"],
        default="1",
    )
    backend = "supabase" if choice in ("1", "supabase") else "postgres"

    env_vars = {"DATABASE_BACKEND": backend}

    if backend == "supabase":
        console.print("\n   Great. You'll need two things from your Supabase dashboard:")
        console.print("   [cyan]Settings -> API Keys[/cyan]")
        console.print(
            "   [yellow]Ogham needs the secret key (formerly service_role) --"
            " the anon key is denied by RLS.[/yellow]"
        )
        default_url = os.environ.get("SUPABASE_URL", "")
        default_key = os.environ.get("SUPABASE_KEY", "")
        if default_url:
            console.print("   [cyan]Found SUPABASE_URL in environment.[/cyan]")
        if default_key:
            console.print("   [cyan]Found SUPABASE_KEY in environment.[/cyan]")
        url = Prompt.ask("   Supabase project URL", default=default_url or None)
        key = Prompt.ask(
            "   Supabase secret key (formerly service_role)",
            default=default_key or None,
        )
        env_vars["SUPABASE_URL"] = url
        env_vars["SUPABASE_KEY"] = key
    else:
        console.print("\n   Great. Paste your PostgreSQL connection string.")
        console.print("   [cyan]Neon: Dashboard -> Connection Details -> Connection string[/cyan]")
        console.print("   [cyan]Format: postgresql://user:pass@host:5432/dbname[/cyan]")
        console.print(
            "   [cyan]Neon tip: pooler endpoint works fine."
            " Use direct endpoint only for schema migrations.[/cyan]"
        )
        default_url = os.environ.get("DATABASE_URL", "")
        if default_url:
            console.print("   [cyan]Found DATABASE_URL in environment.[/cyan]")
        url = Prompt.ask("   Database URL", default=default_url or None)
        env_vars["DATABASE_URL"] = url

    return env_vars


# ---------------------------------------------------------------------------
# Embedding prompts
# ---------------------------------------------------------------------------


def _prompt_embeddings() -> dict:
    """Prompt for embedding provider and credentials."""
    console.print("\n[bold]1. Pick your embedding provider[/bold]")
    console.print("   Ogham turns text into vectors for search. Which provider do you want?\n")
    console.print("   [bold]1)[/bold] ollama   -- free, runs on your machine (must be running)")
    console.print("   [bold]2)[/bold] openai   -- $0.02/1M tokens (API key required)")
    console.print("   [bold]3)[/bold] voyage   -- $0.02/1M tokens + 200M free (API key required)")
    console.print("   [bold]4)[/bold] mistral  -- fixed 1024 dims (API key required)\n")

    provider_map = {"1": "ollama", "2": "openai", "3": "voyage", "4": "mistral"}
    choice = Prompt.ask(
        "   Choose",
        choices=["1", "2", "3", "4", "ollama", "openai", "voyage", "mistral"],
        default="1",
    )
    provider = provider_map.get(choice, choice)

    env_vars = {"EMBEDDING_PROVIDER": provider}

    if provider == "openai":
        default_key = os.environ.get("OPENAI_API_KEY", "")
        if default_key:
            console.print("   [cyan]Found OPENAI_API_KEY in environment.[/cyan]")
        key = Prompt.ask("   OpenAI API key", default=default_key or None)
        env_vars["OPENAI_API_KEY"] = key
    elif provider == "voyage":
        default_key = os.environ.get("VOYAGE_API_KEY", "")
        if default_key:
            console.print("   [cyan]Found VOYAGE_API_KEY in environment.[/cyan]")
        key = Prompt.ask("   Voyage AI API key", default=default_key or None)
        env_vars["VOYAGE_API_KEY"] = key
    elif provider == "mistral":
        default_key = os.environ.get("MISTRAL_API_KEY", "")
        if default_key:
            console.print("   [cyan]Found MISTRAL_API_KEY in environment.[/cyan]")
        key = Prompt.ask("   Mistral API key", default=default_key or None)
        env_vars["MISTRAL_API_KEY"] = key
    elif provider == "ollama":
        default_url = os.environ.get("OLLAMA_URL", "http://localhost:11434")
        url = Prompt.ask("   Ollama URL", default=default_url)
        env_vars["OLLAMA_URL"] = url

    if provider == "mistral":
        env_vars["EMBEDDING_DIM"] = "1024"
        console.print(
            "   [yellow]Mistral uses fixed 1024 dimensions -- schema will be set to 1024.[/yellow]"
        )
    else:
        env_vars["EMBEDDING_DIM"] = "512"

    return env_vars


# ---------------------------------------------------------------------------
# Execution mode
# ---------------------------------------------------------------------------


def _prompt_transport() -> tuple[str, str, int]:
    """Ask transport mode. Returns (transport, host, port)."""
    console.print("\n[bold]3. Server transport[/bold]")
    console.print("   [bold]1)[/bold] Stdio  -- spawned per session (default, works everywhere)")
    console.print(
        "   [bold]2)[/bold] SSE    -- persistent background server (better for multiple agents)\n"
    )

    choice = Prompt.ask(
        "   Choose",
        choices=["1", "2", "stdio", "sse"],
        default="1",
    )

    if choice in ("2", "sse"):
        host = Prompt.ask("   SSE bind host", default="127.0.0.1")
        port_str = Prompt.ask("   SSE port", default="8742")
        return "sse", host, int(port_str)

    return "stdio", "127.0.0.1", 8742


def _prompt_execution_mode() -> str:
    """Ask whether to use uvx or Docker (stdio mode only)."""
    console.print("\n[bold]4. How should your AI clients run Ogham?[/bold]")
    console.print("   [bold]1)[/bold] uvx    -- Python package, lightweight, fast startup")
    console.print("   [bold]2)[/bold] docker -- container image, all dependencies included\n")

    choice = Prompt.ask(
        "   Choose",
        choices=["1", "2", "uvx", "docker"],
        default="1",
    )
    return "uvx" if choice in ("1", "uvx") else "docker"


def _build_mcp_entry(
    env_vars: dict,
    mode: str,
    transport: str = "stdio",
    sse_host: str = "127.0.0.1",
    sse_port: int = 8742,
) -> dict:
    """Build the MCP server config entry for Ogham."""
    if transport == "sse":
        return {"url": f"http://{sse_host}:{sse_port}/sse"}

    env = dict(env_vars)

    if mode == "docker":
        # Inside Docker, localhost means the container -- swap to host
        for key in env:
            env[key] = env[key].replace("localhost", "host.docker.internal")
            env[key] = env[key].replace("127.0.0.1", "host.docker.internal")
        return {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                *[arg for k, v in env.items() for arg in ["-e", f"{k}={v}"]],
                GHCR_IMAGE,
            ],
        }
    else:
        return {
            "command": "uvx",
            "args": ["ogham-mcp"],
            "env": env,
        }


# ---------------------------------------------------------------------------
# Schema migration
# ---------------------------------------------------------------------------


def _find_schema_file(backend: str) -> Path | None:
    """Locate the appropriate schema SQL file."""
    filename = "schema.sql" if backend == "supabase" else "schema_postgres.sql"

    candidates = [
        Path(__file__).parent.parent.parent / "sql" / filename,
        Path(__file__).parent / "sql" / filename,
        Path.cwd() / "sql" / filename,
    ]
    for p in candidates:
        if p.exists():
            return p
    return None


def _adjust_schema_dim(sql: str, dim: str) -> str:
    """Replace vector(512) with the configured dimension if different."""
    if dim and dim != "512":
        return sql.replace("vector(512)", f"vector({dim})")
    return sql


def _run_schema(env_vars: dict) -> bool:
    """Run the appropriate schema SQL against the database."""
    console.print("\n[bold]4. Database schema[/bold]")

    backend = env_vars.get("DATABASE_BACKEND", "supabase")
    dim = env_vars.get("EMBEDDING_DIM", "512")

    if backend == "supabase" and not env_vars.get("DATABASE_URL"):
        console.print("   Ogham needs tables and functions in your database.")
        console.print("   For Supabase, paste the schema SQL into the SQL Editor.")
        schema_path = _find_schema_file("supabase")
        if schema_path and dim != "512":
            # Write a dimension-adjusted copy next to the original
            adjusted_path = schema_path.parent / f"schema_{dim}d.sql"
            adjusted_sql = _adjust_schema_dim(schema_path.read_text(), dim)
            adjusted_path.write_text(adjusted_sql)
            console.print(
                f"   [yellow]Adjusted schema for {dim} dims:[/yellow] [bold]{adjusted_path}[/bold]"
            )
        elif schema_path:
            console.print(f"   Schema file: [bold]{schema_path}[/bold]")
        else:
            console.print("   Schema file: [bold]sql/schema.sql[/bold] (from the repo)")
        console.print(
            "   [cyan]Steps: Supabase dashboard -> SQL Editor -> New query ->"
            " paste contents -> Run[/cyan]"
        )
        if Confirm.ask("   Have you already run the schema?", default=False):
            return True
        console.print(
            "   [cyan]You can run it later. Ogham will work once the schema is in place.[/cyan]"
        )
        return False

    if backend == "postgres":
        db_url = env_vars.get("DATABASE_URL", "")
        if not db_url:
            console.print("   [red]No DATABASE_URL -- skipping schema migration.[/red]")
            return False

        console.print("   We'll create the tables, indexes, and functions for you.")
        if not Confirm.ask("   Run schema now?", default=True):
            return False

        try:
            import psycopg
        except ImportError:
            console.print(
                "   [yellow]psycopg not installed.[/yellow]\n"
                "   [cyan]Reinstall with postgres support:[/cyan]\n"
                "   [cyan]  uvx --from 'ogham-mcp[postgres]' ogham-mcp init[/cyan]"
            )
            console.print("   Run the schema manually: sql/schema_postgres.sql")
            return False

        schema_path = _find_schema_file("postgres")
        if not schema_path:
            console.print(
                "   [red]Schema file not found. Run manually: sql/schema_postgres.sql[/red]"
            )
            return False

        schema_sql = _adjust_schema_dim(schema_path.read_text(), dim)
        if dim != "512":
            console.print(f"   [cyan]Schema adjusted for {dim} dimensions.[/cyan]")
        try:
            with console.status("   Running schema migration..."):
                conn = psycopg.connect(db_url)
                conn.autocommit = True
                conn.execute(schema_sql)
                conn.close()
            console.print("   [green]Schema migration complete.[/green]")
            return True
        except Exception as e:
            console.print(f"   [red]Schema migration failed: {e}[/red]")
            return False

    return False


# ---------------------------------------------------------------------------
# Client configuration
# ---------------------------------------------------------------------------


def _write_codex_toml(config_path: Path, mcp_entry: dict):
    """Write [mcp_servers.ogham] into Codex config.toml.

    Codex sometimes writes non-standard TOML (inline Python dicts),
    so we can't reliably parse the full file. Instead, strip any
    existing [mcp_servers.ogham] block and append a clean one.
    """
    import re

    content = config_path.read_text() if config_path.exists() else ""

    # Remove existing [mcp_servers.ogham] and [mcp_servers.ogham.env]
    # blocks. Match from the header to the next section header or EOF.
    # The args line contains [] so we can't use [^\[]* -- instead match
    # lines that don't start with [
    content = re.sub(
        r"\n*\[mcp_servers\.ogham(?:\.\w+)?\]\n(?:(?!\[).*\n?)*",
        "",
        content,
    )
    content = content.rstrip() + "\n"

    # Build the new ogham section
    lines = ["\n[mcp_servers.ogham]"]
    lines.append(f'command = "{mcp_entry["command"]}"')
    if mcp_entry.get("args"):
        args_str = ", ".join(f'"{a}"' for a in mcp_entry["args"])
        lines.append(f"args = [{args_str}]")
    lines.append("enabled = true")
    if mcp_entry.get("env"):
        lines.append("")
        lines.append("[mcp_servers.ogham.env]")
        for k, v in mcp_entry["env"].items():
            lines.append(f'{k} = "{v}"')

    config_path.write_text(content + "\n".join(lines) + "\n")


def _write_mcp_config(client: dict, mcp_entry: dict):
    """Merge Ogham MCP entry into a client's config file."""
    config_path = client["path"]
    fmt = client["format"]

    config_path.parent.mkdir(parents=True, exist_ok=True)

    if fmt == "codex_toml":
        _write_codex_toml(config_path, mcp_entry)
        return

    if config_path.exists():
        existing = json.loads(config_path.read_text())
    else:
        existing = {}

    if fmt == "opencode":
        # OpenCode uses "mcp" key with a different structure
        if "mcp" not in existing:
            existing["mcp"] = {}
        existing["mcp"]["ogham"] = {
            "type": "local",
            "command": [mcp_entry["command"]] + mcp_entry.get("args", []),
            "environment": mcp_entry.get("env", {}),
            "enabled": True,
        }
    elif fmt == "vscode":
        # VS Code uses "servers" key inside mcp.json
        if "servers" not in existing:
            existing["servers"] = {}
        existing["servers"]["ogham"] = mcp_entry
    else:
        # Standard mcpServers format (Claude Desktop, Claude Code, Cursor)
        if "mcpServers" not in existing:
            existing["mcpServers"] = {}
        existing["mcpServers"]["ogham"] = mcp_entry

    config_path.write_text(json.dumps(existing, indent=2) + "\n")


def _configure_clients(
    env_vars: dict,
    mode: str,
    transport: str = "stdio",
    sse_host: str = "127.0.0.1",
    sse_port: int = 8742,
) -> list[str]:
    """Write MCP config to detected AI clients."""
    step = "5" if transport == "stdio" else "4"
    console.print(f"\n[bold]{step}. Connect your AI clients[/bold]")

    detected = _detect_clients()
    if not detected:
        console.print("   [yellow]No MCP clients detected.[/yellow]")
        console.print(
            "   [cyan]You can configure manually later. See: ogham-mcp.dev/docs/quickstart[/cyan]"
        )
        return []

    console.print(f"   Found {len(detected)} client(s) on your machine.\n")
    if transport == "sse":
        console.print(f"   SSE mode: clients will connect to http://{sse_host}:{sse_port}/sse\n")
    else:
        console.print("   For each one, we'll add Ogham to its MCP config.\n")

    mcp_entry = _build_mcp_entry(env_vars, mode, transport, sse_host, sse_port)
    configured = []

    for client in detected:
        name = client["name"]
        path = client["path"]
        if Confirm.ask(f"   Configure {name}? ({path})", default=True):
            try:
                _write_mcp_config(client, mcp_entry)
                configured.append(name)
                console.print(f"   [green]{name} configured.[/green]")
            except Exception as e:
                console.print(f"   [red]Failed to configure {name}: {e}[/red]")

    return configured


# ---------------------------------------------------------------------------
# Connection test
# ---------------------------------------------------------------------------


def _test_connection(env_vars: dict) -> bool:
    """Validate database and embedding connectivity."""
    console.print("\n[bold]6. Let's check everything works[/bold]")

    # Temporarily set env vars for the health check
    original_env = {}
    for key, value in env_vars.items():
        original_env[key] = os.environ.get(key)
        os.environ[key] = value

    try:
        # Force fresh settings instance from the temp env vars
        from ogham.config import settings

        settings._force()

        # Check database
        with console.status("   Checking database..."):
            from ogham.health import check_database

            db_result = check_database()

        if db_result["status"] == "ok":
            console.print("   [green]Database: connected[/green]")
        else:
            console.print(f"   [red]Database: {db_result.get('error', 'failed')}[/red]")
            if hint := db_result.get("hint"):
                console.print(f"   [cyan]{hint}[/cyan]")
            return False

        # Check embedding provider
        with console.status("   Checking embedding provider..."):
            from ogham.health import check_embedding_provider

            emb_result = check_embedding_provider()

        if emb_result["status"] == "ok":
            prov = env_vars.get("EMBEDDING_PROVIDER", "ollama")
            console.print(f"   [green]Embeddings: {prov} ready[/green]")
        else:
            console.print(
                f"   [yellow]Embeddings: {emb_result.get('error', 'check failed')}[/yellow]"
            )
            if hint := emb_result.get("hint"):
                console.print(f"   [cyan]{hint}[/cyan]")

        return db_result["status"] == "ok"

    finally:
        # Restore original env and settings
        for key, original in original_env.items():
            if original is None:
                os.environ.pop(key, None)
            else:
                os.environ[key] = original
        settings._reset()


# ---------------------------------------------------------------------------
# .env file
# ---------------------------------------------------------------------------


def _write_env_file(env_vars: dict):
    """Optionally write a .env file."""
    if Confirm.ask("\n   Save config to .env file?", default=False):
        env_path = Path.cwd() / ".env"
        lines = [f"{k}={v}" for k, v in env_vars.items()]
        env_path.write_text("\n".join(lines) + "\n")
        console.print(f"   [green]Saved to {env_path}[/green]")
        console.print("   [yellow]Add .env to your .gitignore -- it contains API keys.[/yellow]")


# ---------------------------------------------------------------------------
# Main entry point
# ---------------------------------------------------------------------------


def run_init(
    db_url: str | None = None,
    provider: str | None = None,
    api_key: str | None = None,
    backend: str | None = None,
    supabase_url: str | None = None,
    supabase_key: str | None = None,
    dim: int | None = None,
    mode: str | None = None,
    skip_schema: bool = False,
    skip_clients: bool = False,
    skip_test: bool = False,
):
    """Run the init wizard, interactive or non-interactive."""
    console.clear()
    console.print(
        Panel(
            "[bold]Ogham MCP Setup[/bold]\n\n"
            "This wizard configures your database, embedding provider,\n"
            "and MCP client connections.\n\n"
            "You only need to run this once. Run it again if you want to\n"
            "switch database or embedding provider.\n\n"
            "[cyan]Detected platform: " + platform.system() + "[/cyan]",
            border_style="yellow",
        )
    )

    # Non-interactive mode if key args provided
    if db_url or (supabase_url and supabase_key):
        env_vars = {}
        if db_url:
            env_vars["DATABASE_BACKEND"] = backend or "postgres"
            env_vars["DATABASE_URL"] = db_url
        elif supabase_url:
            env_vars["DATABASE_BACKEND"] = "supabase"
            env_vars["SUPABASE_URL"] = supabase_url
            env_vars["SUPABASE_KEY"] = supabase_key

        env_vars["EMBEDDING_PROVIDER"] = provider or "ollama"
        if api_key:
            key_map = {
                "openai": "OPENAI_API_KEY",
                "voyage": "VOYAGE_API_KEY",
                "mistral": "MISTRAL_API_KEY",
            }
            env_key = key_map.get(env_vars["EMBEDDING_PROVIDER"])
            if env_key:
                env_vars[env_key] = api_key

        env_vars["EMBEDDING_DIM"] = str(dim or 512)
        exec_mode = mode or "uvx"
        transport, sse_host, sse_port = "stdio", "127.0.0.1", 8742
    else:
        # Interactive mode — embeddings first so we know the dimension for schema
        env_vars = _prompt_embeddings()
        env_vars.update(_prompt_database())
        transport, sse_host, sse_port = _prompt_transport()
        if transport == "sse":
            exec_mode = "uvx"  # irrelevant for SSE but needs a value
            env_vars["OGHAM_TRANSPORT"] = "sse"
            env_vars["OGHAM_HOST"] = sse_host
            env_vars["OGHAM_PORT"] = str(sse_port)
        else:
            exec_mode = _prompt_execution_mode()

    # Schema migration
    if not skip_schema:
        _run_schema(env_vars)

    # Configure MCP clients
    configured = []
    if not skip_clients:
        configured = _configure_clients(env_vars, exec_mode, transport, sse_host, sse_port)

    # Test connection
    if not skip_test:
        _test_connection(env_vars)

    # Offer to save .env
    _write_env_file(env_vars)

    # Summary
    if transport == "sse":
        mode_str = f"SSE server on {sse_host}:{sse_port}"
        start_hint = (
            "[cyan]Start the server with:[/cyan] "
            "[bold]ogham serve --transport sse[/bold]\n"
            "[cyan]Then restart your MCP client(s) to connect.[/cyan]"
        )
    else:
        mode_str = "uvx ogham-mcp" if exec_mode == "uvx" else f"docker ({GHCR_IMAGE})"
        start_hint = "[cyan]Restart your MCP client(s) to pick up the new config.[/cyan]"

    console.print(
        Panel(
            "[bold green]You're all set![/bold green]\n\n"
            + (f"Clients configured: {', '.join(configured)}\n" if configured else "")
            + f"Database: {env_vars.get('DATABASE_BACKEND', 'supabase')}\n"
            + f"Embeddings: {env_vars.get('EMBEDDING_PROVIDER', 'ollama')}"
            + f" @ {env_vars.get('EMBEDDING_DIM', '512')}d\n"
            + f"Transport: {mode_str}\n\n"
            + start_hint,
            border_style="green",
        )
    )
