"""Connect command — one-command toolkit + gateway setup for Arcade MCP."""

from __future__ import annotations

import json as _json
import logging
import time
from pathlib import Path
from typing import Any, cast

import httpx
from arcade_core.constants import PROD_COORDINATOR_HOST, PROD_ENGINE_HOST

from arcade_cli.console import console

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Tool catalog cache
# ---------------------------------------------------------------------------

_CACHE_DIR = Path.home() / ".arcade" / "cache"
_CACHE_FILE = _CACHE_DIR / "tools.json"
_CACHE_TTL_SECONDS = 300  # 5 minutes


def _get_context_key() -> str:
    """Return a string identifying the active org+project (for cache scoping)."""
    try:
        from arcade_cli.utils import get_org_project_context

        org_id, project_id = get_org_project_context()
    except Exception:
        return "unknown"
    else:
        return f"{org_id}:{project_id}"


def _read_cache(debug: bool = False) -> dict[str, list[str]] | None:
    """Return cached toolkit map if the cache file exists, is fresh, and matches the active context."""
    try:
        if not _CACHE_FILE.exists():
            return None
        data = _json.loads(_CACHE_FILE.read_text(encoding="utf-8"))
        age = time.time() - data.get("ts", 0)
        if age > _CACHE_TTL_SECONDS:
            if debug:
                console.print(f"  [dim]Cache expired ({age:.0f}s old)[/dim]")
            return None
        # Invalidate if org/project changed
        cached_ctx = data.get("context")
        current_ctx = _get_context_key()
        if cached_ctx and cached_ctx != current_ctx:
            if debug:
                console.print("  [dim]Cache stale (different project context)[/dim]")
            return None
        if debug:
            console.print(f"  [dim]Using cached tool catalog ({age:.0f}s old)[/dim]")
        return cast("dict[str, list[str]]", data.get("toolkits", {}))
    except Exception:
        return None


def _write_cache(toolkits: dict[str, list[str]]) -> None:
    """Persist the toolkit map to disk, scoped to the active org/project."""
    try:
        _CACHE_DIR.mkdir(parents=True, exist_ok=True)
        _CACHE_FILE.write_text(
            _json.dumps({
                "ts": time.time(),
                "context": _get_context_key(),
                "toolkits": toolkits,
            }),
            encoding="utf-8",
        )
    except Exception:
        logger.debug("Failed to write tool cache", exc_info=True)


# ---------------------------------------------------------------------------
# Well-known toolkit metadata
# ---------------------------------------------------------------------------

TOOLKIT_EXAMPLES: dict[str, list[str]] = {
    "github": [
        "List my open pull requests",
        "Show recent issues in my repo",
        "Create a new issue titled 'Bug: login fails'",
    ],
    "slack": [
        "Send a message to #general saying hello",
        "List my unread Slack messages",
        "Search Slack for messages about deployment",
    ],
    "google": [
        "List my upcoming Google Calendar events",
        "Search my Gmail for emails from Alice",
        "Create a new Google Doc titled 'Meeting Notes'",
    ],
    "linear": [
        "Show my assigned Linear issues",
        "Create a new Linear issue for the API refactor",
    ],
    "notion": [
        "Search my Notion workspace for project plans",
        "Create a new Notion page in my workspace",
    ],
    "jira": [
        "List my open Jira tickets",
        "Create a Jira issue for the backend migration",
    ],
    "spotify": [
        "Play my Discover Weekly playlist",
        "What song is currently playing?",
    ],
    "x": [
        "Post a tweet saying 'Hello from Arcade!'",
        "Search recent tweets about AI tools",
    ],
    "reddit": [
        "Search Reddit for posts about MCP tools",
        "Get the top posts from r/programming",
    ],
    "figma": [
        "List my recent Figma files",
        "Get comments on my latest Figma design",
    ],
    "atlassian": [
        "List my Confluence pages",
        "Search Jira for open bugs",
    ],
    "dropbox": [
        "List files in my Dropbox root folder",
        "Search Dropbox for 'project plan'",
    ],
    "asana": [
        "List my Asana tasks",
        "Create a new Asana task for the launch",
    ],
    "hubspot": [
        "List my recent HubSpot contacts",
        "Search HubSpot deals closing this month",
    ],
    "discord": [
        "Send a message to my Discord server",
        "List channels in my Discord server",
    ],
    "zoom": [
        "List my upcoming Zoom meetings",
        "Create a Zoom meeting for tomorrow at 2pm",
    ],
    "microsoft": [
        "List my recent Outlook emails",
        "Search OneDrive for 'quarterly report'",
    ],
    "pagerduty": [
        "List my on-call schedules",
        "Show recent PagerDuty incidents",
    ],
}

PRESET_BUNDLES: dict[str, list[str]] = {
    "Productivity": ["google", "slack", "notion"],
    "Development": ["github", "linear", "jira"],
    "Communication": ["slack", "google", "x"],
    "Project Management": ["linear", "jira", "notion"],
    "DevOps": ["github", "slack", "linear"],
    "Social": ["x", "slack", "reddit"],
    "Creative": ["spotify", "figma", "notion"],
}


def get_toolkit_examples(toolkits: list[str]) -> list[str]:
    """Return example prompts for the given toolkit names."""
    examples: list[str] = []
    for tk in toolkits:
        tk_lower = tk.lower().replace("arcade-", "").replace("arcade_", "")
        if tk_lower in TOOLKIT_EXAMPLES:
            examples.extend(TOOLKIT_EXAMPLES[tk_lower][:2])
    if not examples:
        examples.append("Ask your AI assistant to use one of the configured tools!")
    return examples


# ---------------------------------------------------------------------------
# Login helper
# ---------------------------------------------------------------------------


def ensure_login(coordinator_url: str | None = None) -> str:
    """Ensure the user is logged in, triggering OAuth if needed.

    Returns the valid access token.
    """
    from arcade_cli.authn import (
        OAuthLoginError,
        check_existing_login,
        get_valid_access_token,
        perform_oauth_login,
        save_credentials_from_whoami,
    )

    resolved_url = coordinator_url or f"https://{PROD_COORDINATOR_HOST}"

    if check_existing_login(suppress_message=True):
        return get_valid_access_token(resolved_url)

    console.print("You need to log in to Arcade first.\n", style="yellow")
    try:
        result = perform_oauth_login(
            resolved_url,
            on_status=lambda msg: console.print(msg, style="dim"),
        )
        save_credentials_from_whoami(result.tokens, result.whoami, resolved_url)
        console.print(f"\nLogged in as {result.email}.", style="bold green")
        return get_valid_access_token(resolved_url)
    except OAuthLoginError as e:
        raise SystemExit(f"Login failed: {e}") from e


# ---------------------------------------------------------------------------
# Arcade API helpers
# ---------------------------------------------------------------------------


def fetch_available_toolkits(
    base_url: str | None = None,
    debug: bool = False,
    skip_cache: bool = False,
) -> dict[str, list[str]]:
    """Fetch tools from the Arcade Engine and group them by toolkit name.

    Results are cached to ``~/.arcade/cache/tools.json`` for 5 minutes so
    repeated invocations (e.g. interactive → allow-list) are instant.

    Returns a dict mapping toolkit names to lists of tool qualified names
    (e.g. ``"Github.ListPRs"``).
    """
    if not skip_cache:
        cached = _read_cache(debug=debug)
        if cached is not None:
            return cached

    from arcadepy import NOT_GIVEN, APIConnectionError

    from arcade_cli.utils import compute_base_url, get_arcade_client

    url = base_url or compute_base_url(False, False, PROD_ENGINE_HOST, None, default_port=None)
    if debug:
        console.print(f"  [dim]Connecting to Arcade Engine at {url}[/dim]")
    client = get_arcade_client(url)

    toolkits: dict[str, list[str]] = {}
    tool_count = 0
    try:
        # limit= is the page size, not a cap — the iterator auto-paginates
        for tool in client.tools.list(toolkit=NOT_GIVEN, limit=1000):
            toolkit_name = getattr(tool.toolkit, "name", None) or "unknown"
            tool_name = tool.name or "unknown"
            # Gateway API requires qualified names: "ToolkitName.ToolName"
            qualified = f"{toolkit_name}.{tool_name}"
            toolkits.setdefault(toolkit_name, []).append(qualified)
            tool_count += 1
            if debug:
                console.print(f"  [dim]  Found tool: {qualified}[/dim]")
    except APIConnectionError:
        console.print(f"Could not connect to Arcade Engine at {url}.", style="bold red")
    except Exception as e:
        if debug:
            console.print(f"  [dim]Error fetching toolkits: {e}[/dim]")
        else:
            logger.debug("Failed to fetch toolkits: %s", e)
        console.print(
            "Could not fetch available toolkits from your account.",
            style="bold red",
        )

    if debug:
        console.print(
            f"  [dim]Fetched {tool_count} tools across {len(toolkits)} toolkits: "
            f"{list(toolkits.keys())}[/dim]"
        )

    if toolkits:
        _write_cache(toolkits)

    return toolkits


def list_gateways(
    access_token: str,
    base_url: str | None = None,
    debug: bool = False,
) -> list[dict]:
    """List existing MCP gateways from the user's project.

    Returns a list of gateway dicts (each with ``id``, ``slug``, ``name``,
    ``tool_filter``, etc.).
    """
    from arcade_cli.utils import compute_base_url, get_org_project_context

    url = base_url or compute_base_url(False, False, PROD_ENGINE_HOST, None, default_port=None)
    org_id, project_id = get_org_project_context()

    endpoint = f"{url}/v1/orgs/{org_id}/projects/{project_id}/gateways"

    if debug:
        console.print(f"  [dim]GET {endpoint}[/dim]")

    resp = httpx.get(
        endpoint,
        headers={"Authorization": f"Bearer {access_token}"},
        timeout=30,
    )

    if resp.status_code != 200:
        if debug:
            console.print(f"  [dim]Failed to list gateways: {resp.status_code}[/dim]")
        return []

    data = resp.json()
    return cast("list[dict[Any, Any]]", data.get("items", []))


def find_matching_gateway(
    gateways: list[dict],
    tool_allow_list: list[str],
    auth_type: str = "arcade",
    debug: bool = False,
) -> dict | None:
    """Find an existing gateway whose allow-list is a superset of *tool_allow_list*
    and whose ``auth_type`` matches."""
    needed = set(tool_allow_list)
    for gw in gateways:
        if gw.get("auth_type", "arcade") != auth_type:
            continue
        existing = set(gw.get("tool_filter", {}).get("allowed_tools", []))
        if needed <= existing:
            if debug:
                console.print(
                    f"  [dim]Found existing gateway '{gw.get('slug')}' "
                    f"with {len(existing)} tools (covers all {len(needed)} needed)[/dim]"
                )
            return gw
    return None


def create_gateway(
    access_token: str,
    name: str,
    tool_allow_list: list[str],
    auth_type: str = "arcade",
    slug: str | None = None,
    base_url: str | None = None,
    debug: bool = False,
) -> dict:
    """Create a new MCP gateway on Arcade Cloud.

    Args:
        access_token: OAuth access token for the Engine API.
        name: Human-readable gateway name.
        tool_allow_list: Qualified tool names (e.g. ``"Github.CreateIssue"``).
        auth_type: ``"arcade"`` (OAuth, default) or ``"arcade_header"`` (API key).
        slug: Custom slug for the gateway URL. Auto-generated if not provided.
        base_url: Engine API base URL override.
        debug: Print debug output.

    Returns the gateway response dict (with ``slug``, ``id``, ``name``, etc.).
    """
    from arcade_cli.utils import compute_base_url, get_org_project_context

    url = base_url or compute_base_url(False, False, PROD_ENGINE_HOST, None, default_port=None)
    org_id, project_id = get_org_project_context()

    endpoint = f"{url}/v1/orgs/{org_id}/projects/{project_id}/gateways"
    body: dict = {
        "name": name,
        "auth_type": auth_type,
        "tool_filter": {"allowed_tools": tool_allow_list},
    }
    if slug:
        body["slug"] = slug

    if debug:
        console.print(f"  [dim]POST {endpoint}[/dim]")
        console.print(f"  [dim]Body: {body}[/dim]")

    resp = httpx.post(
        endpoint,
        json=body,
        headers={
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        },
        timeout=30,
    )

    if debug:
        console.print(f"  [dim]Response: {resp.status_code}[/dim]")
        console.print(f"  [dim]{resp.text[:500]}[/dim]")

    if resp.status_code not in (200, 201):
        raise RuntimeError(f"Failed to create gateway ({resp.status_code}): {resp.text}")

    data: dict[Any, Any] = resp.json()

    # The API may return the gateway directly or wrapped in a list/items envelope
    if "slug" in data:
        return data
    if data.get("items"):
        return cast("dict[Any, Any]", data["items"][0])
    if "id" in data:
        return data

    if debug:
        console.print(f"  [dim]Unexpected response shape: {list(data.keys())}[/dim]")
    return data


# ---------------------------------------------------------------------------
# Interactive selection
# ---------------------------------------------------------------------------


def prompt_toolkit_selection(available: dict[str, list[str]]) -> list[str]:
    """Interactively prompt the user to select toolkits.

    Returns a list of selected toolkit names.
    """
    if not available:
        console.print("No toolkits available in your account.", style="bold red")
        raise SystemExit(1)

    console.print("\n[bold]Available toolkits:[/bold]\n")

    # Case-insensitive lookup: preset says "github", API returns "Github"
    avail_lower: dict[str, str] = {k.lower(): k for k in available}

    # Show preset bundles first
    bundle_choices: list[tuple[str, list[str]]] = []
    for bundle_name, bundle_tks in PRESET_BUNDLES.items():
        # Resolve each preset toolkit to its actual API key
        matching = [avail_lower[t] for t in bundle_tks if t in avail_lower]
        if matching:
            bundle_choices.append((bundle_name, matching))

    sorted_toolkits = sorted(available.keys())

    # Number the options
    idx = 1
    option_map: dict[int, list[str]] = {}

    for bundle_name, bundle_tks in bundle_choices:
        tool_count = sum(len(available.get(t, [])) for t in bundle_tks)
        display_names = ", ".join(t.lower() for t in bundle_tks)
        console.print(
            f"  [bold cyan]{idx}.[/bold cyan] {bundle_name} bundle "
            f"({display_names}) — {tool_count} tools"
        )
        option_map[idx] = bundle_tks
        idx += 1

    if bundle_choices:
        console.print()

    for tk_name in sorted_toolkits:
        tools = available[tk_name]
        console.print(f"  [bold cyan]{idx}.[/bold cyan] {tk_name} — {len(tools)} tools")
        option_map[idx] = [tk_name]
        idx += 1

    console.print(f"\n  [bold cyan]{idx}.[/bold cyan] All available toolkits")
    option_map[idx] = sorted_toolkits

    console.print()
    try:
        raw = input("Select toolkits (comma-separated numbers, e.g. 1,3): ").strip()
    except (EOFError, KeyboardInterrupt):
        raise SystemExit("\nCancelled.")

    selected: list[str] = []
    for part in raw.split(","):
        part = part.strip()
        if not part:
            continue
        try:
            choice = int(part)
        except ValueError:
            console.print(f"  Skipping invalid choice: {part}", style="yellow")
            continue
        if choice in option_map:
            for tk in option_map[choice]:
                if tk not in selected:
                    selected.append(tk)
        else:
            console.print(f"  Skipping unknown option: {choice}", style="yellow")

    if not selected:
        console.print("No toolkits selected.", style="bold red")
        raise SystemExit(1)

    return selected


# ---------------------------------------------------------------------------
# Main orchestrator
# ---------------------------------------------------------------------------


def run_connect(
    client: str,
    toolkits: list[str] | None = None,
    tools: list[str] | None = None,
    gateway: str | None = None,
    all_tools: bool = False,
    gateway_slug: str | None = None,
    config_path: Path | None = None,
    debug: bool = False,
) -> None:
    """Run the quickstart flow: login → determine mode → configure client.

    Everything is configured as a cloud gateway — no local server required.

    Args:
        toolkits: Whole toolkit names (e.g. ``["github"]``) — adds all tools.
        tools: Individual qualified tool names (e.g. ``["Github.CreateIssue"]``).
        gateway: Existing gateway slug to connect to directly.
    """

    # Step 1: Ensure login
    access_token = ensure_login()

    # Step 2: Determine mode
    if gateway:
        # --- Direct gateway mode (existing gateway) ---
        # Resolve the input: user may pass a name ("opencode") or slug ("pascal_opencode")
        slug = _resolve_gateway_slug(gateway, access_token, debug=debug)
        _configure_gateway(client, slug, config_path, name=gateway)
        return

    # --- Toolkit / tool → gateway mode ---

    # If only --tool is given (no --toolkit, --all, or interactive), skip catalog fetch
    if tools and not toolkits and not all_tools:
        tool_allow_list = list(tools)
        selected_toolkits = sorted({t.split(".")[0] for t in tools})
        console.print(
            f"\nSetting up gateway with {len(tool_allow_list)} individual tool(s): "
            f"[bold]{', '.join(tool_allow_list)}[/bold]\n"
        )
    else:
        selected_toolkits_list: list[str]

        # Fetch the tool catalog once — used for selection and allow-list
        console.print("Fetching tool catalog...", style="dim")
        available = fetch_available_toolkits(debug=debug)

        if toolkits:
            selected_toolkits_list = toolkits
        elif all_tools:
            if not available:
                console.print(
                    "No toolkits found. Make sure you have tools deployed in your Arcade account.",
                    style="bold red",
                )
                raise SystemExit(1)
            selected_toolkits_list = sorted(available.keys())
        else:
            # Interactive mode
            if not available:
                console.print(
                    "No toolkits found in your account. You can specify toolkits manually:\n"
                    "  [bold]arcade connect claude-code --toolkit github[/bold]",
                    style="yellow",
                )
                raise SystemExit(1)
            selected_toolkits_list = prompt_toolkit_selection(available)

        selected_toolkits = selected_toolkits_list

        console.print(
            f"\nSetting up gateway for toolkits: [bold]{', '.join(selected_toolkits)}[/bold]\n"
        )

        # Build a case-insensitive lookup: "github" -> "Github", etc.
        tk_lower_map: dict[str, str] = {k.lower(): k for k in available}

        if debug:
            console.print(f"  [dim]Available toolkit keys: {list(available.keys())}[/dim]")
            console.print(f"  [dim]Looking for: {selected_toolkits}[/dim]")

        tool_allow_list = []
        for tk in selected_toolkits:
            actual_key = tk_lower_map.get(tk.lower())
            if actual_key:
                tk_tools = available[actual_key]
                tool_allow_list.extend(tk_tools)
                if debug:
                    console.print(
                        f"  [dim]Matched '{tk}' -> '{actual_key}' ({len(tk_tools)} tools)[/dim]"
                    )
            else:
                console.print(f"  [yellow]Warning: No tools found for toolkit '{tk}'.[/yellow]")
                if available:
                    console.print(
                        f"  [yellow]Available toolkits: "
                        f"{', '.join(sorted(available.keys()))}[/yellow]"
                    )

        # Append any individual --tool names
        if tools:
            for t in tools:
                if t not in tool_allow_list:
                    tool_allow_list.append(t)
            if debug:
                console.print(f"  [dim]Added {len(tools)} individual tool(s)[/dim]")

    if not tool_allow_list:
        console.print(
            "\nNo tools to add to the gateway. Deploy toolkits first with "
            "[bold]arcade deploy[/bold].",
            style="bold red",
        )
        raise SystemExit(1)

    # Check if an existing gateway already covers these tools
    auth_type = "arcade"
    console.print("Checking existing gateways...", style="dim")
    existing_gateways = list_gateways(access_token, debug=debug)
    existing = find_matching_gateway(
        existing_gateways, tool_allow_list, auth_type=auth_type, debug=debug
    )

    if existing:
        slug = existing["slug"]
        console.print(
            f"  Found existing gateway: [bold]{existing.get('name', slug)}[/bold] (slug: {slug})\n",
            style="green",
        )
    else:
        # Create a new gateway
        if len(selected_toolkits) == 1:
            gateway_name = selected_toolkits[0].lower()
        else:
            gateway_name = "-".join(sorted({tk.lower() for tk in selected_toolkits}))
        console.print(
            f"Creating gateway '{gateway_name}' with {len(tool_allow_list)} tools "
            f"(auth: {auth_type})...",
            style="dim",
        )

        gw = create_gateway(
            access_token=access_token,
            name=gateway_name,
            tool_allow_list=tool_allow_list,
            auth_type=auth_type,
            slug=gateway_slug,
            debug=debug,
        )

        slug = gw.get("slug", gateway_name)
        if debug:
            console.print(f"  [dim]Gateway response: id={gw.get('id')}, slug={slug}[/dim]")
        console.print(f"  Gateway created: [bold]{slug}[/bold]\n", style="green")

    # Config key: prefer --slug if given, otherwise derive from toolkit names
    if gateway_slug:
        display_name = gateway_slug
    elif len(selected_toolkits) == 1:
        display_name = selected_toolkits[0].lower()
    else:
        display_name = "-".join(sorted({tk.lower() for tk in selected_toolkits}))
    _configure_gateway(client, slug, config_path, name=display_name)

    # Print examples
    examples = get_toolkit_examples(selected_toolkits)
    console.print("\nTry asking your AI assistant:", style="bold")
    for ex in examples[:3]:
        console.print(f"   - {ex}", style="dim")


def _resolve_gateway_slug(
    user_input: str,
    access_token: str,
    debug: bool = False,
) -> str:
    """Resolve a gateway name or slug to the actual slug.

    The user may pass a name (``opencode``) or a slug (``pascal_opencode``).
    We look up existing gateways and match by slug first, then by name.
    Falls back to the original input if no match is found.
    """
    gateways = list_gateways(access_token, debug=debug)
    input_lower = user_input.lower()
    for gw in gateways:
        if gw.get("slug", "").lower() == input_lower:
            if debug:
                console.print(f"  [dim]Matched by slug: {gw['slug']}[/dim]")
            return cast("str", gw["slug"])
    for gw in gateways:
        if gw.get("name", "").lower() == input_lower:
            slug = gw["slug"]
            if debug:
                console.print(f"  [dim]Matched by name '{gw['name']}' -> slug: {slug}[/dim]")
            return cast("str", slug)
    if debug:
        available = [f"{g.get('name')} ({g.get('slug')})" for g in gateways]
        console.print(f"  [dim]No match for '{user_input}', available: {available}[/dim]")
    return user_input


def _configure_gateway(
    client: str,
    slug: str,
    config_path: Path | None,
    name: str | None = None,
) -> None:
    """Configure the MCP client to connect to a gateway by slug.

    *name* is the human-readable label used as the config key (e.g. ``github``).
    Defaults to *slug* if not provided.
    """
    from arcade_cli.configure import configure_client_gateway
    from arcade_cli.utils import compute_base_url

    api_base = compute_base_url(False, False, PROD_ENGINE_HOST, None, default_port=None)
    gateway_url = f"{api_base}/mcp/{slug}"
    server_name = name or slug

    console.print(f"Configuring [bold]{client}[/bold] to connect to gateway: [bold]{slug}[/bold]\n")

    configure_client_gateway(
        client=client,
        server_name=server_name,
        gateway_url=gateway_url,
        auth_token=None,
        config_path=config_path,
    )

    console.print("\n[bold green]Setup complete![/bold green]")
    console.print(f"   Gateway URL: {gateway_url}", style="dim")
    console.print("   Auth: OAuth (handled by your MCP client)", style="dim")
