"""gsearch setup — browser profile setup + MCP server configuration for AI tools."""

from __future__ import annotations

import asyncio
import json
import platform
import shutil
import subprocess
from pathlib import Path

import click
from rich.console import Console
from rich.table import Table

from gsearch_mcp_cli.constants import CHROME_PROFILE_DIR
from gsearch_mcp_cli.core.browser import launch_chrome, shutdown_chrome
from gsearch_mcp_cli.core.warmup import mark_warmed_up, needs_warmup, warmup_profile

console = Console()

MCP_SERVER_CMD = "gsearch-mcp"

CLIENT_REGISTRY = {
    "claude-code": {
        "name": "Claude Code",
        "description": "Anthropic CLI (claude command)",
        "method": "cli",
    },
    "gemini-cli": {
        "name": "Gemini CLI",
        "description": "Google Gemini CLI",
        "method": "json",
        "config_path_fn": lambda: Path.home() / ".gemini" / "settings.json",
        "server_key": "gsearch",
        "extra_fields": {"trust": True},
    },
    "cursor": {
        "name": "Cursor",
        "description": "Cursor AI editor",
        "method": "json",
        "config_path_fn": lambda: _cursor_config_path(),
    },
    "windsurf": {
        "name": "Windsurf",
        "description": "Codeium Windsurf editor",
        "method": "json",
        "config_path_fn": lambda: _windsurf_config_path(),
    },
    "cline": {
        "name": "Cline",
        "description": "Cline CLI terminal agent",
        "method": "json",
        "config_path_fn": lambda: Path.home() / ".cline" / "data" / "settings" / "cline_mcp_settings.json",
    },
    "antigravity": {
        "name": "Antigravity",
        "description": "Google Antigravity AI IDE",
        "method": "json",
        "config_path_fn": lambda: Path.home() / ".gemini" / "antigravity" / "mcp_config.json",
        "server_key": "gsearch",
    },
    "codex": {
        "name": "Codex CLI",
        "description": "OpenAI Codex CLI",
        "method": "cli_codex",
    },
    "opencode": {
        "name": "OpenCode",
        "description": "OpenCode terminal AI assistant",
        "method": "json_opencode",
        "config_path_fn": lambda: Path.home() / ".config" / "opencode" / "opencode.json",
    },
}


def _cursor_config_path() -> Path:
    system = platform.system()
    if system == "Darwin":
        return Path.home() / ".cursor" / "mcp.json"
    elif system == "Windows":
        import os
        return Path(os.environ.get("APPDATA", "")) / "Cursor" / "User" / "mcp.json"
    return Path.home() / ".config" / "cursor" / "mcp.json"


def _windsurf_config_path() -> Path:
    system = platform.system()
    if system == "Darwin":
        return Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
    elif system == "Windows":
        import os
        return Path(os.environ.get("APPDATA", "")) / "Codeium" / "windsurf" / "mcp_config.json"
    return Path.home() / ".config" / "codeium" / "windsurf" / "mcp_config.json"


def _find_mcp_server_path() -> str | None:
    return shutil.which(MCP_SERVER_CMD)


def _build_server_entry(use_full_path: bool = False) -> dict:
    if use_full_path:
        binary = _find_mcp_server_path() or MCP_SERVER_CMD
        return {"command": binary, "args": []}
    return {"command": MCP_SERVER_CMD, "args": []}


def _add_to_json_config(
    config_path: Path, server_key: str, server_entry: dict, extra_fields: dict | None = None,
) -> None:
    """Add MCP server to a JSON config file (mcpServers format)."""
    config_path.parent.mkdir(parents=True, exist_ok=True)

    config = {}
    if config_path.exists():
        try:
            config = json.loads(config_path.read_text())
        except json.JSONDecodeError:
            config = {}

    if "mcpServers" not in config:
        config["mcpServers"] = {}

    entry = dict(server_entry)
    if extra_fields:
        entry.update(extra_fields)

    config["mcpServers"][server_key] = entry
    config_path.write_text(json.dumps(config, indent=2) + "\n")


def _add_to_opencode(config_path: Path, server_entry: dict) -> None:
    """Add MCP server to OpenCode config (different format)."""
    config_path.parent.mkdir(parents=True, exist_ok=True)

    config = {}
    if config_path.exists():
        try:
            config = json.loads(config_path.read_text())
        except json.JSONDecodeError:
            config = {}

    if "mcp" not in config:
        config["mcp"] = {}

    config["mcp"]["gsearch"] = {
        "type": "local",
        "command": [server_entry.get("command", MCP_SERVER_CMD)],
        "enabled": True,
        "timeout": 300000,
    }

    if "experimental" not in config:
        config["experimental"] = {}
    config["experimental"]["mcp_timeout"] = 300000

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


def _remove_from_json_config(config_path: Path, server_key: str) -> bool:
    """Remove MCP server from JSON config. Returns True if found and removed."""
    if not config_path.exists():
        return False
    try:
        config = json.loads(config_path.read_text())
    except json.JSONDecodeError:
        return False

    removed = False
    if "mcpServers" in config and server_key in config["mcpServers"]:
        del config["mcpServers"][server_key]
        removed = True
    if "mcp" in config and "gsearch" in config["mcp"]:
        del config["mcp"]["gsearch"]
        removed = True

    if removed:
        config_path.write_text(json.dumps(config, indent=2) + "\n")
    return removed


# -- Profile warmup (async) --

async def _run_warmup() -> None:
    console.print("[bold blue]Launching browser...[/]")
    proc, port = await launch_chrome()
    try:
        from gsearch_mcp_cli.core.cdp import BrowserCDP
        cdp = BrowserCDP()
        await cdp.connect(port)
        try:
            console.print("[bold blue]Running profile warmup...[/]")
            await warmup_profile(cdp)
            mark_warmed_up(CHROME_PROFILE_DIR)
            console.print("[bold green]Profile warmed up successfully[/]")
        finally:
            await cdp.close()
    finally:
        shutdown_chrome(proc, port)


# -- CLI Commands --

@click.group("setup", invoke_without_command=True)
@click.option("--reset", is_flag=True, help="Delete existing profile and re-warmup from scratch.")
@click.option("--check", is_flag=True, help="Verify the profile is already warmed up.")
@click.pass_context
def setup_cmd(ctx, reset: bool, check: bool) -> None:
    """Set up Chrome profile and MCP server configuration.

    Without a subcommand, runs browser profile warmup.
    Use 'gsearch setup add <tool>' to configure MCP for AI tools.
    """
    if ctx.invoked_subcommand is not None:
        return

    if check:
        if not needs_warmup(CHROME_PROFILE_DIR):
            console.print("[bold green]Profile is warmed up and ready[/]")
        else:
            console.print("[bold red]Profile needs warmup -- run: gsearch setup[/]")
            raise SystemExit(1)
        return

    if reset:
        if CHROME_PROFILE_DIR.exists():
            console.print(f"[yellow]Removing profile directory: {CHROME_PROFILE_DIR}[/]")
            shutil.rmtree(CHROME_PROFILE_DIR)
        else:
            console.print("[dim]No existing profile to remove.[/]")

    console.print("[bold]Starting profile setup...[/]")
    try:
        asyncio.run(_run_warmup())
    except Exception as exc:
        console.print(f"[bold red]Setup failed:[/] {exc}")
        raise SystemExit(1)


@setup_cmd.command("add")
@click.argument("tool", type=click.Choice(list(CLIENT_REGISTRY.keys()) + ["json", "all"]))
def setup_add(tool: str) -> None:
    """Add gsearch MCP server to a tool's configuration.

    Configures the specified AI tool to use gsearch-mcp as an MCP server.
    """
    if tool == "json":
        _print_json_config()
        return

    if tool == "all":
        _setup_all()
        return

    client = CLIENT_REGISTRY[tool]
    method = client["method"]
    server_entry = _build_server_entry()

    try:
        if method == "cli":
            _setup_claude_code(server_entry)
        elif method == "cli_codex":
            _setup_codex(server_entry)
        elif method == "json_opencode":
            config_path = client["config_path_fn"]()
            _add_to_opencode(config_path, server_entry)
            console.print(f"[green]Added gsearch-mcp to {client['name']} at {config_path}[/green]")
        elif method == "json":
            config_path = client["config_path_fn"]()
            server_key = client.get("server_key", "gsearch-mcp")
            extra = client.get("extra_fields")
            _add_to_json_config(config_path, server_key, server_entry, extra)
            console.print(f"[green]Added gsearch-mcp to {client['name']} at {config_path}[/green]")
    except Exception as e:
        console.print(f"[red]Failed to configure {client['name']}:[/red] {e}")
        raise SystemExit(1)


@setup_cmd.command("remove")
@click.argument("tool", type=click.Choice(list(CLIENT_REGISTRY.keys())))
def setup_remove(tool: str) -> None:
    """Remove gsearch MCP server from a tool's configuration."""
    client = CLIENT_REGISTRY[tool]
    method = client["method"]

    if method == "cli":
        try:
            subprocess.run(["claude", "mcp", "remove", "gsearch-mcp"], check=True, capture_output=True)
            console.print(f"[green]Removed gsearch-mcp from {client['name']}[/green]")
        except (subprocess.CalledProcessError, FileNotFoundError):
            console.print(f"[yellow]Could not remove from {client['name']} via CLI[/yellow]")
        return

    if method == "cli_codex":
        console.print("[yellow]Remove gsearch-mcp from Codex config manually[/yellow]")
        return

    config_path = client.get("config_path_fn", lambda: None)()
    if config_path:
        server_key = client.get("server_key", "gsearch-mcp")
        if _remove_from_json_config(config_path, server_key):
            console.print(f"[green]Removed gsearch-mcp from {client['name']}[/green]")
        else:
            console.print(f"[dim]gsearch-mcp not found in {client['name']} config[/dim]")


@setup_cmd.command("list")
def setup_list() -> None:
    """List supported AI tools for MCP setup."""
    table = Table(title="MCP Setup Targets")
    table.add_column("Tool", style="bold")
    table.add_column("Name")
    table.add_column("Description", style="dim")

    for tool_id, client in CLIENT_REGISTRY.items():
        table.add_row(tool_id, client["name"], client["description"])

    table.add_row("json", "JSON", "Print JSON config snippet", style="dim")
    table.add_row("all", "All", "Interactive setup for multiple tools", style="dim")
    console.print(table)


def _setup_claude_code(server_entry: dict) -> None:
    cmd_parts = ["claude", "mcp", "add", "-s", "user", "gsearch-mcp", "--", server_entry["command"]]
    try:
        subprocess.run(cmd_parts, check=True, capture_output=True, text=True)
        console.print("[green]Added gsearch-mcp to Claude Code[/green]")
    except FileNotFoundError:
        console.print("[red]'claude' command not found. Install Claude Code first.[/red]")
        raise SystemExit(1)
    except subprocess.CalledProcessError as e:
        console.print(f"[red]Failed: {e.stderr or e.stdout}[/red]")
        raise SystemExit(1)


def _setup_codex(server_entry: dict) -> None:
    cmd_parts = ["codex", "mcp", "add", "gsearch-mcp", "--", server_entry["command"]]
    try:
        subprocess.run(cmd_parts, check=True, capture_output=True, text=True)
        console.print("[green]Added gsearch-mcp to Codex CLI[/green]")
    except FileNotFoundError:
        console.print("[red]'codex' command not found. Install Codex CLI first.[/red]")
        raise SystemExit(1)
    except subprocess.CalledProcessError as e:
        console.print(f"[red]Failed: {e.stderr or e.stdout}[/red]")
        raise SystemExit(1)


def _print_json_config() -> None:
    server_entry = _build_server_entry()
    full_path = _find_mcp_server_path()

    console.print("\n[bold]MCP Server Configuration[/bold]\n")

    console.print("[dim]Using command name (requires gsearch-mcp in PATH):[/dim]")
    snippet = {"mcpServers": {"gsearch-mcp": server_entry}}
    console.print(json.dumps(snippet, indent=2))

    if full_path:
        console.print(f"\n[dim]Using full path ({full_path}):[/dim]")
        snippet_full = {"mcpServers": {"gsearch-mcp": {"command": full_path, "args": []}}}
        console.print(json.dumps(snippet_full, indent=2))

    console.print("\n[dim]Using uvx (no install required):[/dim]")
    snippet_uvx = {"mcpServers": {"gsearch-mcp": {
        "command": "uvx",
        "args": ["--from", "gsearch-mcp-cli", "gsearch-mcp"],
    }}}
    console.print(json.dumps(snippet_uvx, indent=2))

    if platform.system() == "Darwin":
        try:
            subprocess.run(
                ["pbcopy"],
                input=json.dumps(snippet, indent=2),
                text=True,
                check=True,
                capture_output=True,
            )
            console.print("\n[green]Copied to clipboard (command name version)[/green]")
        except (FileNotFoundError, subprocess.CalledProcessError):
            pass


def _setup_all() -> None:
    console.print("[bold]Available tools:[/bold]\n")
    for tool_id, client in CLIENT_REGISTRY.items():
        console.print(f"  {tool_id:<16} {client['name']}")
    console.print()
    console.print("[dim]Run 'gsearch setup add <tool>' for each tool you want to configure.[/dim]")
