"""Interactive setup CLI for non-technical users.

Configures Claude Desktop and Claude Code to use the BPA MCP server.
Auto-discovers Keycloak authentication from just a BPA URL.

Usage:
    mcp-eregistrations-bpa setup https://bpa.gateway.nipc.gov.ng
    mcp-eregistrations-bpa setup  # shows help
"""

from __future__ import annotations

import json
import platform
import shutil
import sys
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

import httpx

# ANSI colors
_GREEN = "\033[0;32m"
_YELLOW = "\033[1;33m"
_BLUE = "\033[0;34m"
_RED = "\033[0;31m"
_BOLD = "\033[1m"
_NC = "\033[0m"

# Known country-name-to-ISO mappings for .eregistrations.org subdomains
_COUNTRY_CODES: dict[str, str] = {
    "els": "SV",
    "elsalvador": "SV",
    "kenya": "KE",
    "cuba": "CU",
    "jamaica": "JM",
    "colombia": "CO",
    "nigeria": "NG",
    "lesotho": "LS",
    "gambia": "GM",
    "bhutan": "BT",
    "rwanda": "RW",
    "tanzania": "TZ",
    "cameroon": "CM",
    "benin": "BJ",
    "mali": "ML",
    "senegal": "SN",
    "togo": "TG",
    "guinea": "GN",
    "niger": "NE",
    "chad": "TD",
    "djibouti": "DJ",
    "mauritania": "MR",
    "burundi": "BI",
    "madagascar": "MG",
    "myanmar": "MM",
    "laos": "LA",
    "cambodia": "KH",
    "vietnam": "VN",
}

PACKAGE_NAME = "mcp-eregistrations-bpa"


def _print_banner() -> None:
    print()
    print(f"{_BLUE}{'=' * 56}{_NC}")
    print(f"{_BLUE}    eRegistrations BPA - Claude Setup{_NC}")
    print(f"{_BLUE}{'=' * 56}{_NC}")
    print()


def _print_success_banner() -> None:
    print()
    print(f"{_GREEN}{'=' * 56}{_NC}")
    print(f"{_GREEN}    Setup Complete!{_NC}")
    print(f"{_GREEN}{'=' * 56}{_NC}")
    print()


def _derive_keycloak_url(bpa_url: str) -> str:
    """Derive Keycloak URL by replacing 'bpa.' prefix with 'login.' in hostname.

    This pattern holds for 100% of known Keycloak BPA instances.
    """
    parsed = urlparse(bpa_url)
    hostname = parsed.netloc
    if hostname.startswith("bpa."):
        login_host = "login." + hostname[4:]
    else:
        # Fallback: prepend login. to hostname
        login_host = "login." + hostname
    return f"https://{login_host}"


def _get_realm_candidates(bpa_url: str) -> list[str]:
    """Generate realm candidates to probe, ordered by likelihood.

    Strategy:
    1. ccTLD as uppercase (e.g. .ng → NG, .ke → KE)
    2. ccTLD as lowercase
    3. Country name from subdomain → ISO code lookup
    4. Common short codes from hostname parts
    """
    parsed = urlparse(bpa_url)
    hostname = parsed.netloc.lower()
    parts = hostname.split(".")

    candidates: list[str] = []
    seen: set[str] = set()

    def _add(code: str) -> None:
        if code and code not in seen:
            seen.add(code)
            candidates.append(code)

    # 1. TLD as realm (works for ccTLDs: .ng, .ke, .ls, .gm, .bt)
    tld = parts[-1] if parts else ""
    if len(tld) == 2:
        _add(tld.upper())
        _add(tld.lower())

    # 2. Country name from subdomain parts → ISO lookup
    # Skip common prefixes/suffixes
    skip = {
        "bpa",
        "login",
        "www",
        "eregistrations",
        "org",
        "com",
        "gov",
        "test",
        "dev",
        "staging",
    }
    meaningful = [p for p in parts if p not in skip and len(p) > 1]
    for part in meaningful:
        # Direct lookup
        if part in _COUNTRY_CODES:
            code = _COUNTRY_CODES[part]
            _add(code)
            _add(code.lower())
        # Try without common suffixes (e.g., "stagingibls" won't match)
        for prefix_len in range(3, len(part)):
            prefix = part[:prefix_len]
            if prefix in _COUNTRY_CODES:
                code = _COUNTRY_CODES[prefix]
                _add(code)
                _add(code.lower())
                break

    # 3. Two-letter parts from hostname as guesses
    for part in meaningful:
        if len(part) == 2:
            _add(part.upper())
            _add(part.lower())

    return candidates


def _probe_keycloak_realm(keycloak_url: str, realm: str, timeout: float = 5.0) -> bool:
    """Probe if a Keycloak realm exists by hitting its well-known endpoint."""
    url = f"{keycloak_url}/realms/{realm}/.well-known/openid-configuration"
    try:
        resp = httpx.get(url, timeout=timeout, follow_redirects=True)
        if resp.status_code == 200:
            data = resp.json()
            return "issuer" in data
    except (httpx.RequestError, ValueError, KeyError):
        pass
    return False


def _discover_realm(keycloak_url: str, bpa_url: str) -> str | None:
    """Auto-discover the Keycloak realm by probing candidates."""
    candidates = _get_realm_candidates(bpa_url)
    if not candidates:
        return None

    print(f"  Probing Keycloak at {_BOLD}{keycloak_url}{_NC}...")
    for realm in candidates:
        sys.stdout.write(f"    Trying realm '{realm}'... ")
        sys.stdout.flush()
        if _probe_keycloak_realm(keycloak_url, realm):
            print(f"{_GREEN}found!{_NC}")
            return realm
        print("no")
    return None


def _resolve_uvx_path() -> str:
    """Resolve the absolute path to uvx for Claude Desktop (GUI apps need full path)."""
    uvx_path = shutil.which("uvx")
    if uvx_path:
        return uvx_path
    # Check common Homebrew locations
    for candidate in [
        "/opt/homebrew/bin/uvx",  # Apple Silicon
        "/usr/local/bin/uvx",  # Intel Mac
        str(Path.home() / ".local" / "bin" / "uvx"),  # curl install
        str(Path.home() / ".cargo" / "bin" / "uvx"),  # cargo install
    ]:
        if Path(candidate).exists():
            return candidate
    return "uvx"  # Fallback


def _get_claude_desktop_config_path() -> Path:
    """Get Claude Desktop config path for the current platform."""
    if platform.system() == "Darwin":
        return (
            Path.home()
            / "Library"
            / "Application Support"
            / "Claude"
            / "claude_desktop_config.json"
        )
    elif platform.system() == "Windows":
        return (
            Path.home()
            / "AppData"
            / "Roaming"
            / "Claude"
            / "claude_desktop_config.json"
        )
    else:
        return Path.home() / ".config" / "Claude" / "claude_desktop_config.json"


def _get_claude_code_config_path() -> Path:
    """Get Claude Code global config path."""
    return Path.home() / ".claude.json"


def _build_server_name(bpa_url: str) -> str:
    """Build a human-friendly server name from the BPA URL."""
    parsed = urlparse(bpa_url)
    hostname = parsed.netloc.lower()
    parts = hostname.split(".")

    skip = {"bpa", "eregistrations", "org", "com", "www"}
    meaningful = [p for p in parts if p not in skip and p]

    if meaningful:
        # Reverse to get country first: e.g. ["dev", "els"] → "elsalvador-dev"
        name = "-".join(reversed(meaningful[:3]))
    else:
        name = hostname.replace(".", "-")

    return f"BPA-{name}"


def _build_keycloak_env(bpa_url: str, keycloak_url: str, realm: str) -> dict[str, str]:
    return {
        "BPA_INSTANCE_URL": bpa_url,
        "KEYCLOAK_URL": keycloak_url,
        "KEYCLOAK_REALM": realm,
        "KEYCLOAK_CLIENT_ID": PACKAGE_NAME,
    }


def _build_cas_env(
    bpa_url: str,
    cas_url: str,
    client_id: str,
    client_secret: str,
) -> dict[str, str]:
    return {
        "BPA_INSTANCE_URL": bpa_url,
        "CAS_URL": cas_url,
        "CAS_CLIENT_ID": client_id,
        "CAS_CLIENT_SECRET": client_secret,
    }


def _write_config(
    config_path: Path,
    server_name: str,
    server_config: dict[str, Any],
) -> bool:
    """Write or merge MCP server config into a Claude config file.

    Returns True on success.
    """
    config_path.parent.mkdir(parents=True, exist_ok=True)

    if config_path.exists():
        try:
            existing = json.loads(config_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            existing = {}

        # Backup existing
        backup = config_path.with_suffix(".backup.json")
        try:
            shutil.copy2(config_path, backup)
        except OSError:
            pass
    else:
        existing = {}

    # Ensure mcpServers key
    if "mcpServers" not in existing:
        existing["mcpServers"] = {}

    existing["mcpServers"][server_name] = server_config

    try:
        config_path.write_text(
            json.dumps(existing, indent=2, ensure_ascii=False) + "\n",
            encoding="utf-8",
        )
        return True
    except OSError as e:
        print(f"  {_RED}Error writing {config_path}: {e}{_NC}")
        return False


def _get_existing_secret(server_name: str) -> str | None:
    """Check existing Claude configs for a saved CAS_CLIENT_SECRET."""
    config_paths = [_get_claude_desktop_config_path(), _get_claude_code_config_path()]
    for config_path in config_paths:
        if not config_path.exists():
            continue
        try:
            data = json.loads(config_path.read_text(encoding="utf-8"))
            env = data.get("mcpServers", {}).get(server_name, {}).get("env", {})
            secret: str = env.get("CAS_CLIENT_SECRET", "")
            if secret:
                return secret
        except (json.JSONDecodeError, OSError):
            continue
    return None


def _configure_claude(
    server_name: str,
    env_vars: dict[str, str],
) -> None:
    """Write config to both Claude Desktop and Claude Code."""
    uvx_path = _resolve_uvx_path()

    # Claude Desktop — needs absolute path to uvx
    # @latest forces uvx to always fetch newest version from PyPI
    desktop_config = {
        "command": uvx_path,
        "args": [f"{PACKAGE_NAME}@latest"],
        "env": env_vars,
    }
    desktop_path = _get_claude_desktop_config_path()
    if _write_config(desktop_path, server_name, desktop_config):
        print(f"  {_GREEN}Claude Desktop{_NC} configured ({desktop_path})")

    # Claude Code — can use plain "uvx" since terminal has PATH
    code_config = {
        "command": "uvx",
        "args": [f"{PACKAGE_NAME}@latest"],
        "env": env_vars,
    }
    code_path = _get_claude_code_config_path()
    if _write_config(code_path, server_name, code_config):
        print(f"  {_GREEN}Claude Code{_NC}    configured ({code_path})")


def _setup_keycloak(bpa_url: str) -> bool:
    """Setup with Keycloak auto-discovery. Returns True on success."""
    keycloak_url = _derive_keycloak_url(bpa_url)

    print()
    print(f"{_BLUE}Auto-discovering authentication...{_NC}")

    realm = _discover_realm(keycloak_url, bpa_url)

    if not realm:
        print()
        print(f"{_YELLOW}Could not auto-detect realm.{_NC}")
        print("  Ask your BPA administrator for the Keycloak realm code")
        print("  (usually a 2-letter country code like NG, KE, SV).")
        print()
        try:
            realm = input("  Keycloak realm: ").strip()
        except (EOFError, KeyboardInterrupt):
            print()
            return False

        if not realm:
            print(f"  {_RED}No realm provided. Setup cancelled.{_NC}")
            return False

        # Verify the user-provided realm
        sys.stdout.write(f"  Verifying realm '{realm}'... ")
        sys.stdout.flush()
        if _probe_keycloak_realm(keycloak_url, realm):
            print(f"{_GREEN}OK{_NC}")
        else:
            print(f"{_YELLOW}could not verify (may still work){_NC}")

    server_name = _build_server_name(bpa_url)
    env_vars = _build_keycloak_env(bpa_url, keycloak_url, realm)

    print()
    print(f"{_BLUE}Configuring Claude...{_NC}")
    print(f"  Server name:  {_BOLD}{server_name}{_NC}")
    print(f"  BPA URL:      {bpa_url}")
    print(f"  Keycloak:     {keycloak_url}")
    print(f"  Realm:        {realm}")
    print()

    _configure_claude(server_name, env_vars)
    return True


def _setup_cas(
    bpa_url: str,
    cas_url_override: str | None = None,
    client_id_override: str | None = None,
) -> bool:
    """Setup with CAS. Pre-filled values skip prompts; reuses existing secret."""
    server_name = _build_server_name(bpa_url)

    # Use overrides or prompt
    cas_url = cas_url_override
    client_id = client_id_override

    if not cas_url or not client_id:
        print()
        print(f"{_YELLOW}CAS authentication detected (legacy system).{_NC}")
        print("  You'll need these from your BPA administrator:")
        if not cas_url:
            print("  - CAS URL (e.g. https://eid.xxx.eregistrations.org/cback/v1.0)")
        if not client_id:
            print("  - Client ID (usually 'mcp-bpa')")
        print("  - Client Secret")
        print()

        try:
            if not cas_url:
                cas_url = input("  CAS URL: ").strip()
                if not cas_url:
                    print(f"  {_RED}No CAS URL provided. Setup cancelled.{_NC}")
                    return False

            if not client_id:
                client_id = input("  Client ID [mcp-bpa]: ").strip() or "mcp-bpa"
        except (EOFError, KeyboardInterrupt):
            print()
            return False
    else:
        print()
        print(f"  CAS URL:      {cas_url}")
        print(f"  Client ID:    {client_id}")

    # Check for existing secret in current config
    existing_secret = _get_existing_secret(server_name)
    if existing_secret:
        print(f"  {_GREEN}Reusing existing client secret from config{_NC}")
        client_secret = existing_secret
    else:
        try:
            import getpass

            client_secret = getpass.getpass("  Client Secret: ").strip()
            if not client_secret:
                print(f"  {_RED}No client secret provided. Setup cancelled.{_NC}")
                return False
        except (EOFError, KeyboardInterrupt):
            print()
            return False

    env_vars = _build_cas_env(bpa_url, cas_url, client_id, client_secret)

    print()
    print(f"{_BLUE}Configuring Claude...{_NC}")
    print(f"  Server name:  {_BOLD}{server_name}{_NC}")
    print(f"  BPA URL:      {bpa_url}")
    print(f"  CAS URL:      {cas_url}")
    print()

    _configure_claude(server_name, env_vars)
    return True


def _detect_auth_type(bpa_url: str) -> str:
    """Try to detect whether the BPA instance uses Keycloak or CAS.

    Probes the derived Keycloak URL first. If it responds, it's Keycloak.
    Otherwise, checks for CAS at the BPA URL.
    """
    keycloak_url = _derive_keycloak_url(bpa_url)

    # Try Keycloak first — check if the server responds at all
    try:
        resp = httpx.get(keycloak_url, timeout=5.0, follow_redirects=True)
        if resp.status_code < 500:
            return "keycloak"
    except httpx.RequestError:
        pass

    # Check for CAS at the BPA URL
    cas_url = f"{bpa_url}/cback/v1.0"
    try:
        resp = httpx.head(cas_url, timeout=5.0, follow_redirects=True)
        if resp.status_code < 500:
            return "cas"
    except httpx.RequestError:
        pass

    # Default to keycloak (most common)
    return "keycloak"


def run_setup() -> None:
    """Main entry point for `mcp-eregistrations-bpa setup`."""
    _print_banner()

    # Parse BPA URL from arguments
    args = sys.argv[2:]  # skip "mcp-eregistrations-bpa" and "setup"

    if not args or args[0] in ("--help", "-h"):
        print("  Configure Claude Desktop and Claude Code for a BPA instance.")
        print()
        print(f"  {_BOLD}Usage:{_NC}")
        print("    mcp-eregistrations-bpa setup <BPA_URL>")
        print()
        print(f"  {_BOLD}Example:{_NC}")
        print("    mcp-eregistrations-bpa setup https://bpa.gateway.nipc.gov.ng")
        print()
        print("  The BPA URL is provided by your administrator.")
        print("  Authentication is auto-detected from the URL.")
        print()
        print(f"  {_BOLD}Options:{_NC}")
        print("    --keycloak-url URL   Override auto-detected Keycloak URL")
        print("    --realm CODE         Override auto-detected realm")
        print("    --cas                Force CAS authentication mode")
        print("    --cas-url URL        CAS URL (skips CAS URL prompt)")
        print("    --client-id ID       CAS client ID (skips client ID prompt)")
        print()
        sys.exit(0)

    # Parse arguments
    bpa_url: str | None = None
    keycloak_url_override: str | None = None
    realm_override: str | None = None
    cas_url_override: str | None = None
    client_id_override: str | None = None
    force_cas = False

    i = 0
    while i < len(args):
        arg = args[i]
        if arg == "--keycloak-url" and i + 1 < len(args):
            keycloak_url_override = args[i + 1]
            i += 2
        elif arg == "--realm" and i + 1 < len(args):
            realm_override = args[i + 1]
            i += 2
        elif arg == "--cas-url" and i + 1 < len(args):
            cas_url_override = args[i + 1]
            force_cas = True
            i += 2
        elif arg == "--client-id" and i + 1 < len(args):
            client_id_override = args[i + 1]
            i += 2
        elif arg == "--cas":
            force_cas = True
            i += 1
        elif not arg.startswith("-"):
            bpa_url = arg
            i += 1
        else:
            print(f"{_RED}Unknown option: {arg}{_NC}")
            sys.exit(1)

    if not bpa_url:
        print(f"{_RED}Error: BPA URL is required.{_NC}")
        print("  Usage: mcp-eregistrations-bpa setup <BPA_URL>")
        sys.exit(1)

    # Normalize URL
    if not bpa_url.startswith("https://"):
        if bpa_url.startswith("http://"):
            bpa_url = "https://" + bpa_url[7:]
        else:
            bpa_url = "https://" + bpa_url
    bpa_url = bpa_url.rstrip("/")

    print(f"  BPA instance: {_BOLD}{bpa_url}{_NC}")

    # Handle explicit overrides
    if keycloak_url_override and realm_override:
        server_name = _build_server_name(bpa_url)
        env_vars = _build_keycloak_env(bpa_url, keycloak_url_override, realm_override)
        print()
        print(f"{_BLUE}Configuring Claude...{_NC}")
        print(f"  Server name:  {_BOLD}{server_name}{_NC}")
        _configure_claude(server_name, env_vars)
        _print_success_banner()
        _print_next_steps()
        return

    if force_cas:
        success = _setup_cas(bpa_url, cas_url_override, client_id_override)
    else:
        # Auto-detect auth type
        print()
        sys.stdout.write("  Detecting authentication type... ")
        sys.stdout.flush()
        auth_type = _detect_auth_type(bpa_url)
        print(f"{_GREEN}{auth_type.upper()}{_NC}")

        if auth_type == "keycloak":
            success = _setup_keycloak(bpa_url)
        else:
            success = _setup_cas(bpa_url, cas_url_override, client_id_override)

    if success:
        _print_success_banner()
        _print_next_steps()
    else:
        print()
        print(f"{_RED}Setup was not completed.{_NC}")
        sys.exit(1)


def _print_next_steps() -> None:
    print(f"  {_BLUE}Next steps:{_NC}")
    print("  1. Restart Claude Desktop (quit and reopen)")
    print("  2. The BPA server will appear in your MCP tools")
    print('  3. Ask Claude: "Log me in to BPA"')
    print()
    print(f"  {_BLUE}Need help?{_NC}")
    print("  Ask your BPA administrator or visit:")
    print("  https://github.com/UNCTAD-eRegistrations/MCP_eRegistrations")
    print()
