#!/usr/bin/env python3
"""
Meta Ads MCP Setup Wizard — works after `pip install meta-ads-mcp-server`.

Auto-detects MCP clients, install mode (uvx/pip/dev), and generates
the correct configuration. No repo clone needed.

Usage:
    meta-ads-setup                        # Interactive
    meta-ads-setup --token XXX --account act_123 --clients claude
"""

from __future__ import annotations

import argparse
import io
import json
import os
import platform
import shutil
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

# Ensure stdout handles Unicode on all platforms
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")

IS_WINDOWS = platform.system() == "Windows"
IS_MACOS = platform.system() == "Darwin"
IS_LINUX = platform.system() == "Linux"


# ---------------------------------------------------------------------------
# MCP Client Definitions
# ---------------------------------------------------------------------------

@dataclass
class MCPClient:
    """An MCP-compatible client."""
    name: str
    display_name: str
    description: str
    config_paths: dict[str, list[str]]
    config_format: str  # "json" or "jsonc"
    mcp_key: str
    detect_dirs: dict[str, list[str]] = field(default_factory=dict)

    def get_config_path(self) -> Path | None:
        plat = "windows" if IS_WINDOWS else "macos" if IS_MACOS else "linux"
        paths = self.config_paths.get(plat, [])
        expanded = [Path(os.path.expandvars(os.path.expanduser(p))) for p in paths]

        for path in expanded:
            if path.exists():
                return path
        for path in expanded:
            if path.parent.exists():
                return path
        return None

    def is_installed(self) -> bool:
        plat = "windows" if IS_WINDOWS else "macos" if IS_MACOS else "linux"
        detect_paths = self.detect_dirs.get(plat, [])
        if not detect_paths:
            config_path = self.get_config_path()
            return config_path is not None and config_path.exists()
        for path_template in detect_paths:
            path = Path(os.path.expandvars(os.path.expanduser(path_template)))
            if path.exists():
                return True
        return False


CLIENTS = [
    MCPClient(
        name="claude",
        display_name="Claude Desktop",
        description="Anthropic's Claude Desktop app",
        config_paths={
            "windows": [r"%APPDATA%\Claude\claude_desktop_config.json"],
            "macos": ["~/Library/Application Support/Claude/claude_desktop_config.json"],
            "linux": ["~/.config/Claude/claude_desktop_config.json"],
        },
        config_format="json",
        mcp_key="mcpServers",
        detect_dirs={
            "windows": [r"%APPDATA%\Claude"],
            "macos": ["~/Library/Application Support/Claude"],
            "linux": ["~/.config/Claude"],
        },
    ),
    MCPClient(
        name="cursor",
        display_name="Cursor",
        description="Cursor AI Editor",
        config_paths={
            "windows": [r"%APPDATA%\Cursor\mcp.json", r"%USERPROFILE%\.cursor\mcp.json"],
            "macos": ["~/Library/Application Support/Cursor/mcp.json", "~/.cursor/mcp.json"],
            "linux": ["~/.config/Cursor/mcp.json", "~/.cursor/mcp.json"],
        },
        config_format="json",
        mcp_key="mcpServers",
        detect_dirs={
            "windows": [r"%APPDATA%\Cursor", r"%USERPROFILE%\.cursor"],
            "macos": ["~/Library/Application Support/Cursor", "~/.cursor"],
            "linux": ["~/.config/Cursor", "~/.cursor"],
        },
    ),
    MCPClient(
        name="vscode",
        display_name="VS Code",
        description="Visual Studio Code",
        config_paths={
            "windows": [r"%APPDATA%\Code\User\settings.json"],
            "macos": ["~/Library/Application Support/Code/User/settings.json"],
            "linux": ["~/.config/Code/User/settings.json"],
        },
        config_format="jsonc",
        mcp_key="mcp.servers",
        detect_dirs={
            "windows": [r"%APPDATA%\Code"],
            "macos": ["~/Library/Application Support/Code"],
            "linux": ["~/.config/Code"],
        },
    ),
    MCPClient(
        name="windsurf",
        display_name="Windsurf",
        description="Windsurf AI Editor",
        config_paths={
            "windows": [r"%APPDATA%\Windsurf\mcp.json"],
            "macos": ["~/Library/Application Support/Windsurf/mcp.json"],
            "linux": ["~/.config/Windsurf/mcp.json"],
        },
        config_format="json",
        mcp_key="mcpServers",
        detect_dirs={
            "windows": [r"%APPDATA%\Windsurf"],
            "macos": ["~/Library/Application Support/Windsurf"],
            "linux": ["~/.config/Windsurf"],
        },
    ),
    MCPClient(
        name="gemini",
        display_name="Gemini CLI",
        description="Google Gemini CLI",
        config_paths={
            "windows": [r"%USERPROFILE%\.gemini\mcp.json"],
            "macos": ["~/.gemini/mcp.json"],
            "linux": ["~/.gemini/mcp.json"],
        },
        config_format="json",
        mcp_key="servers",
        detect_dirs={
            "windows": [r"%USERPROFILE%\.gemini"],
            "macos": ["~/.gemini"],
            "linux": ["~/.gemini"],
        },
    ),
]


# ---------------------------------------------------------------------------
# Install Mode Detection
# ---------------------------------------------------------------------------

def detect_install_mode() -> str:
    """Detect how meta-ads-mcp-server was installed.

    Returns: "uvx", "pip", or "dev"
    """
    # Check if uvx is available
    uvx_available = shutil.which("uvx") is not None

    # Check if we're running from a site-packages install
    this_file = Path(__file__).resolve()
    in_site_packages = "site-packages" in str(this_file)

    if in_site_packages and uvx_available:
        return "uvx"
    elif in_site_packages:
        return "pip"
    else:
        return "dev"


def _find_dev_python_dir() -> Path | None:
    """Find the python/ dir for dev mode (running from cloned repo)."""
    this_file = Path(__file__).resolve()
    # setup_wizard.py is at python/src/setup_wizard.py
    python_dir = this_file.parent.parent
    if (python_dir / "pyproject.toml").exists():
        return python_dir
    return None


# ---------------------------------------------------------------------------
# Config Generation
# ---------------------------------------------------------------------------

def create_mcp_config(
    credentials: dict[str, str],
    install_mode: str,
) -> dict[str, Any]:
    """Create the MCP server config dict based on install mode."""
    env_vars: dict[str, str] = {}

    if credentials.get("access_token"):
        env_vars["META_ACCESS_TOKEN"] = credentials["access_token"]
    if credentials.get("ad_account_id"):
        env_vars["META_DEFAULT_AD_ACCOUNT_ID"] = credentials["ad_account_id"]
    if credentials.get("app_secret"):
        env_vars["META_APP_SECRET"] = credentials["app_secret"]

    if install_mode == "uvx":
        return {
            "command": "uvx",
            "args": ["meta-ads-mcp-server"],
            "env": env_vars,
        }
    elif install_mode == "pip":
        return {
            "command": "meta-ads-mcp",
            "env": env_vars,
        }
    else:
        # Dev mode — needs cwd pointing to clone
        python_dir = _find_dev_python_dir()
        if python_dir is None:
            # Fallback to pip mode
            return {
                "command": "meta-ads-mcp",
                "env": env_vars,
            }
        cwd = str(python_dir).replace("\\", "/") if IS_WINDOWS else str(python_dir)
        command = "python" if IS_WINDOWS else "python3"
        return {
            "command": command,
            "args": ["-m", "src.server"],
            "cwd": cwd,
            "env": env_vars,
        }


# ---------------------------------------------------------------------------
# JSON/JSONC Helpers
# ---------------------------------------------------------------------------

def _strip_jsonc_comments(content: str) -> str:
    """Strip comments from JSONC content (string-aware)."""
    result = []
    i = 0
    in_string = False

    while i < len(content):
        char = content[i]

        if in_string:
            result.append(char)
            if char == '\\':
                i += 1
                if i < len(content):
                    result.append(content[i])
            elif char == '"':
                in_string = False
            i += 1
            continue

        if char == '"':
            in_string = True
            result.append(char)
            i += 1
            continue

        if char == '/' and i + 1 < len(content):
            next_char = content[i + 1]
            if next_char == '/':
                while i < len(content) and content[i] != '\n':
                    i += 1
                continue
            elif next_char == '*':
                i += 2
                while i + 1 < len(content):
                    if content[i] == '*' and content[i + 1] == '/':
                        i += 2
                        break
                    i += 1
                else:
                    i = len(content)
                continue

        result.append(char)
        i += 1

    return ''.join(result)


def _detect_indent(content: str) -> int:
    import re
    match = re.search(r'\n( +)"', content)
    return len(match.group(1)) if match else 2


# ---------------------------------------------------------------------------
# Config File Updates
# ---------------------------------------------------------------------------

def update_client_config(client: MCPClient, server_config: dict[str, Any]) -> bool:
    """Update a client's MCP configuration file."""
    config_path = client.get_config_path()
    if not config_path:
        print(f"  Could not find config path for {client.display_name}")
        return False

    try:
        indent = 2
        if config_path.exists():
            content = config_path.read_text(encoding="utf-8-sig")
            indent = _detect_indent(content)

            backup_path = config_path.with_suffix(config_path.suffix + ".bak")
            shutil.copy2(config_path, backup_path)

            if client.config_format == "jsonc":
                content = _strip_jsonc_comments(content)
            try:
                config = json.loads(content)
            except json.JSONDecodeError as e:
                print(f"  Warning: Could not parse {config_path} ({e})")
                print(f"  Backup saved to {backup_path}")
                config = {}
        else:
            config = {}

        # Navigate to the correct key
        current = config
        keys = client.mcp_key.split(".")
        for key in keys[:-1]:
            if key not in current:
                current[key] = {}
            current = current[key]

        final_key = keys[-1]
        if final_key not in current:
            current[final_key] = {}
        current[final_key]["meta-ads"] = server_config

        config_path.parent.mkdir(parents=True, exist_ok=True)
        config_path.write_text(json.dumps(config, indent=indent) + "\n", encoding="utf-8")

        print(f"  Configured {client.display_name} -> {config_path}")
        return True

    except Exception as e:
        print(f"  Failed to update {client.display_name}: {e}")
        return False


# ---------------------------------------------------------------------------
# Interactive Prompts
# ---------------------------------------------------------------------------

def select_clients_interactive(available: list[MCPClient]) -> list[MCPClient]:
    if not available:
        print("\nNo MCP clients detected. A generic config will be printed instead.\n")
        return []

    print(f"\nDetected {len(available)} MCP client(s):\n")
    for i, client in enumerate(available, 1):
        print(f"  [{i}] {client.display_name}")
    print(f"  [A] All")
    print(f"  [N] None (print config to copy manually)")
    print()

    while True:
        choice = input("Select (comma-separated numbers, A, or N): ").strip().upper()
        if choice == "N":
            return []
        if choice == "A":
            return available
        try:
            indices = [int(x.strip()) for x in choice.split(",")]
            selected = []
            for idx in indices:
                if 1 <= idx <= len(available):
                    selected.append(available[idx - 1])
                else:
                    print(f"  Invalid: {idx}")
                    break
            else:
                return selected
        except ValueError:
            print("  Enter numbers, A, or N.")


def get_credentials_interactive() -> dict[str, str]:
    print("\n--- Meta API Credentials ---")
    print("Get a token from: https://business.facebook.com/settings/system-users\n")

    access_token = input("Meta Access Token: ").strip()
    if not access_token:
        print("Error: Access token is required.")
        sys.exit(1)

    ad_account = input("Ad Account ID (e.g. act_123456789): ").strip()
    if ad_account and not ad_account.lower().startswith("act_"):
        ad_account = f"act_{ad_account}"

    return {
        "access_token": access_token,
        "ad_account_id": ad_account,
        "method": "token",
    }


def select_install_mode_interactive(detected: str) -> str:
    print(f"\nDetected install mode: {detected}")
    print()
    print("  [1] uvx (recommended - zero install, always up to date)")
    print("  [2] pip (uses installed meta-ads-mcp-server command)")
    print("  [3] dev (local clone with python -m src.server)")
    print()

    choice = input(f"Select [1/2/3] (default: 1): ").strip()
    return {"2": "pip", "3": "dev"}.get(choice, "uvx")


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(
        description="Meta Ads MCP Setup Wizard",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  meta-ads-setup                                      # Interactive
  meta-ads-setup --token XXX --account act_123        # Non-interactive, all detected clients
  meta-ads-setup --token XXX --account act_123 --clients claude,cursor
  meta-ads-setup --list-clients
  meta-ads-setup --mode uvx --token XXX --account act_123
        """,
    )
    parser.add_argument("--token", help="Meta Access Token")
    parser.add_argument("--account", help="Ad Account ID (e.g. act_123456789)")
    parser.add_argument("--clients", help="Comma-separated clients: claude,cursor,vscode,windsurf,gemini")
    parser.add_argument("--mode", choices=["uvx", "pip", "dev"], help="Install mode for config generation")
    parser.add_argument("--list-clients", action="store_true", help="List detected MCP clients and exit")

    args = parser.parse_args()

    print()
    print("Meta Ads MCP - Setup Wizard")
    print(f"Platform: {platform.system()} | Home: {Path.home()}")
    print()

    if args.list_clients:
        print("MCP Clients:")
        for client in CLIENTS:
            status = "[installed]" if client.is_installed() else "[not found]"
            print(f"  {client.name:12} {client.display_name:20} {status}")
        return

    # Determine install mode
    detected_mode = detect_install_mode()
    if args.mode:
        install_mode = args.mode
    elif args.token:
        # Non-interactive: use detected mode, prefer uvx
        install_mode = detected_mode if detected_mode != "dev" else "uvx"
    else:
        install_mode = select_install_mode_interactive(detected_mode)

    # Get credentials
    if args.token:
        ad_account = args.account or ""
        if ad_account and not ad_account.lower().startswith("act_"):
            ad_account = f"act_{ad_account}"
        credentials = {
            "access_token": args.token,
            "ad_account_id": ad_account,
            "method": "token",
        }
    else:
        credentials = get_credentials_interactive()

    # Build config
    server_config = create_mcp_config(credentials, install_mode)

    # Determine clients
    if args.clients:
        client_names = [c.strip().lower() for c in args.clients.split(",")]
        valid_names = {c.name for c in CLIENTS}
        invalid = [n for n in client_names if n not in valid_names]
        if invalid:
            print(f"Error: Unknown client(s): {', '.join(invalid)}")
            print(f"Valid: {', '.join(sorted(valid_names))}")
            sys.exit(1)
        selected = [c for c in CLIENTS if c.name in client_names]
    elif args.token:
        # Non-interactive: configure all detected clients
        selected = [c for c in CLIENTS if c.is_installed()]
    else:
        available = [c for c in CLIENTS if c.is_installed()]
        selected = select_clients_interactive(available)

    # Apply configs
    if selected:
        print()
        for client in selected:
            update_client_config(client, server_config)
    else:
        # Print config for manual copy
        print("\nAdd this to your MCP client config:\n")
        wrapper = {"mcpServers": {"meta-ads": server_config}}
        print(json.dumps(wrapper, indent=2))

    # Done
    print()
    print("Done! Restart your MCP client to apply changes.")
    print('Test: Ask "Can you check my Meta Ads system status?"')
    print()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\nCancelled.")
        sys.exit(130)
    except EOFError:
        print("\n\nNo input. Use --help for non-interactive options.")
        sys.exit(1)
