"""connect_customer_org -- Authorize a customer sandbox org.

Connects to a customer's Salesforce sandbox for metadata retrieval and
technical impact analysis. Hard-blocks production orgs (safety rule).
Compares sandbox packages against production licenses.json if available.
"""

import json
import logging
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from ..utils import sf_cli, state
from ..utils.pe_helpers import DEFAULT_NAMESPACE_MAPPING

logger = logging.getLogger(__name__)

DEFAULT_LOGIN_URL = "https://test.salesforce.com"

_CS_NAMESPACES: set[str] = {
    entry["namespace"].lower() for entry in DEFAULT_NAMESPACE_MAPPING
}

TOOL_DEFINITION = {
    "name": "connect_customer_org",
    "description": (
        "Authorize a customer Salesforce sandbox for metadata retrieval. "
        "Accepts a flexible org identifier (alias, org ID, or instance URL). "
        "Hard-blocks production orgs. Compares sandbox packages against "
        "production license data. Must be called BEFORE list_metadata or "
        "retrieve_metadata."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "account_name": {
                "type": "string",
                "description": (
                    "Customer account name (e.g. 'NBN Co Ltd'). Links this "
                    "sandbox to the customer folder in customers/<slug>/."
                ),
            },
            "org_identifier": {
                "type": "string",
                "description": (
                    "Flexible identifier for the sandbox. Can be an alias "
                    "(e.g. 'nbn-psup'), an org ID (starts with '00D'), or "
                    "an instance URL (e.g. 'https://nbnco--psup.sandbox.my"
                    ".salesforce.com'). The tool auto-detects the format."
                ),
            },
            "org_alias": {
                "type": "string",
                "description": (
                    "Alias to assign when logging in fresh. Defaults to "
                    "'<slug>-sandbox'. Ignored if the org is already authorized."
                ),
            },
            "working_directory": {
                "type": "string",
                "description": "Workspace directory. The AI passes this automatically.",
            },
        },
        "required": ["account_name"],
    },
}


def _make_slug(name: str) -> str:
    slug = name.lower().strip()
    slug = re.sub(r"[^a-z0-9]+", "-", slug)
    return slug.strip("-") or "unknown"


def _detect_identifier_type(identifier: str) -> str:
    """Detect whether the identifier is an org ID, URL, or alias."""
    stripped = identifier.strip()
    if stripped.upper().startswith("00D"):
        return "org_id"
    if stripped.startswith("http"):
        return "url"
    return "alias"


def _extract_instance_url(url: str) -> str:
    """Extract the base instance URL from a full Salesforce URL."""
    url = url.strip().rstrip("/")
    if "/lightning" in url or "/apex" in url or "/one/" in url:
        url = url.split("/lightning")[0].split("/apex")[0].split("/one/")[0]
    return url


def _resolve_by_org_id(org_id: str) -> dict[str, Any] | None:
    """Find an authorized org by org ID in sf org list."""
    try:
        orgs = sf_cli.list_orgs()
        for org in orgs:
            if (org.get("orgId") or "").upper() == org_id.upper():
                return org
    except sf_cli.SfCliError:
        pass
    return None


def _resolve_by_url(url: str) -> dict[str, Any] | None:
    """Find an authorized org by instance URL in sf org list."""
    instance = _extract_instance_url(url).lower()
    try:
        orgs = sf_cli.list_orgs()
        for org in orgs:
            org_instance = (org.get("instanceUrl") or "").lower().rstrip("/")
            if org_instance and instance.startswith(org_instance):
                return org
            if org_instance and org_instance.startswith(instance):
                return org
    except sf_cli.SfCliError:
        pass
    return None


def _resolve_by_alias(alias: str) -> dict[str, Any] | None:
    """Try sf org display directly with the alias."""
    try:
        data = sf_cli.display_org(alias)
        return data.get("result", {})
    except sf_cli.SfCliError:
        pass
    try:
        orgs = sf_cli.list_orgs()
        alias_lower = alias.lower()
        for org in orgs:
            org_alias = (org.get("alias") or "").lower()
            if org_alias == alias_lower:
                return org
    except sf_cli.SfCliError:
        pass
    return None


def _validate_org(alias: str) -> dict[str, Any] | None:
    """Get full org info via sf org display."""
    try:
        data = sf_cli.display_org(alias)
        return data.get("result", {})
    except sf_cli.SfCliError:
        return None


def _is_sandbox(org_info: dict[str, Any]) -> bool:
    """Check if the org is a sandbox using both isSandbox flag and URL."""
    if org_info.get("isSandbox") is True:
        return True
    instance_url = (org_info.get("instanceUrl") or "").lower()
    if ".sandbox." in instance_url or "--" in instance_url:
        return True
    return False


def _match_cs_packages(
    sandbox_packages: list[dict[str, Any]],
) -> list[dict[str, str]]:
    """Match sandbox packages against known CS namespaces."""
    cs_packages = []
    for pkg in sandbox_packages:
        ns = (pkg.get("SubscriberPackageNamespace") or "").strip()
        if ns and ns.lower() in _CS_NAMESPACES:
            cs_packages.append({
                "namespace": ns,
                "name": pkg.get("SubscriberPackageName", ""),
                "version": pkg.get("SubscriberPackageVersionNumber", ""),
                "version_name": pkg.get("SubscriberPackageVersionName", ""),
                "package_id": pkg.get("SubscriberPackageId", ""),
            })
    return cs_packages


def _compare_with_production(
    sandbox_cs_packages: list[dict[str, str]],
    working_dir: Path,
    slug: str,
) -> dict[str, Any] | None:
    """Compare sandbox CS packages against production licenses.json."""
    licenses_path = working_dir / "customers" / slug / "licenses.json"
    if not licenses_path.exists():
        return None

    try:
        licenses_data = json.loads(licenses_path.read_text())
    except (json.JSONDecodeError, OSError):
        return None

    prod_packages: dict[str, dict[str, str]] = {}
    for org_data in licenses_data.get("production_orgs", []):
        for pkg in org_data.get("packages", []):
            ns = (pkg.get("namespace") or "").strip()
            if ns:
                ns_lower = ns.lower()
                if ns_lower not in prod_packages:
                    prod_packages[ns_lower] = {
                        "namespace": ns,
                        "name": pkg.get("pe_name") or pkg.get("name", ""),
                        "version": pkg.get("installed_version", ""),
                    }

    sandbox_by_ns = {
        p["namespace"].lower(): p for p in sandbox_cs_packages
    }

    details = []
    counts = {
        "matching": 0,
        "sandbox_behind": 0,
        "sandbox_ahead": 0,
        "missing_in_sandbox": 0,
        "only_in_sandbox": 0,
    }

    all_namespaces = set(prod_packages.keys()) | set(sandbox_by_ns.keys())
    for ns_lower in sorted(all_namespaces):
        prod = prod_packages.get(ns_lower)
        sand = sandbox_by_ns.get(ns_lower)

        if prod and not sand:
            counts["missing_in_sandbox"] += 1
            details.append({
                "namespace": prod["namespace"],
                "package_name": prod["name"],
                "production_version": prod["version"],
                "sandbox_version": None,
                "status": "MISSING_IN_SANDBOX",
            })
        elif sand and not prod:
            counts["only_in_sandbox"] += 1
            details.append({
                "namespace": sand["namespace"],
                "package_name": sand["name"],
                "production_version": None,
                "sandbox_version": sand["version"],
                "status": "ONLY_IN_SANDBOX",
            })
        elif prod and sand:
            prod_ver = prod["version"]
            sand_ver = sand["version"]
            if prod_ver == sand_ver:
                status = "MATCHING"
                counts["matching"] += 1
            elif _version_tuple(sand_ver) < _version_tuple(prod_ver):
                status = "SANDBOX_BEHIND"
                counts["sandbox_behind"] += 1
            else:
                status = "SANDBOX_AHEAD"
                counts["sandbox_ahead"] += 1
            details.append({
                "namespace": prod["namespace"],
                "package_name": prod["name"],
                "production_version": prod_ver,
                "sandbox_version": sand_ver,
                "status": status,
            })

    return {
        "compared_against": "licenses.json",
        **counts,
        "details": details,
    }


def _version_tuple(ver: str) -> tuple[int, ...]:
    """Parse a version string into a comparable tuple."""
    parts = []
    for part in ver.split("."):
        try:
            parts.append(int(part))
        except ValueError:
            parts.append(0)
    return tuple(parts) or (0,)


def _try_browser_login(alias: str, login_url: str) -> str | None:
    """Open browser login. Returns None on success, or error message."""
    from .connect_lma import _kill_port_1717

    max_attempts = 2
    for attempt in range(1, max_attempts + 1):
        try:
            sf_cli.run_sf(
                f"org login web --alias {alias} --instance-url {login_url}",
                timeout=60,
                json_output=False,
            )
            return None
        except sf_cli.SfCliError as e:
            error_str = str(e)
            if "1717" in error_str and attempt < max_attempts:
                _kill_port_1717()
                continue
            if "TimeoutExpired" in str(e.stderr):
                _kill_port_1717()
                return (
                    "STATUS: LOGIN_TIMEOUT\n\n"
                    "The browser was opened but the user did not complete "
                    "login in time.\n\n"
                    "Ask the user to try again. If the browser is still open, "
                    "they can finish there.\n\n"
                    "Manual command:\n"
                    f"sf org login web --alias {alias} "
                    f"--instance-url {login_url}\n\n"
                    "Call connect_customer_org again once ready."
                )
            return (
                "STATUS: LOGIN_FAILED\n\n"
                f"Browser login failed: {e}\n\n"
                "Manual command:\n"
                f"sf org login web --alias {alias} "
                f"--instance-url {login_url}\n\n"
                "Call connect_customer_org again once ready."
            )
    return None


def _create_sfdx_project(metadata_dir: Path) -> None:
    """Create a minimal SFDX project scaffold for metadata retrieval."""
    metadata_dir.mkdir(parents=True, exist_ok=True)

    sfdx_project = metadata_dir / "sfdx-project.json"
    if not sfdx_project.exists():
        sfdx_project.write_text(json.dumps({
            "packageDirectories": [{"path": "force-app", "default": True}],
            "namespace": "",
            "sfdcLoginUrl": "https://test.salesforce.com",
            "sourceApiVersion": "61.0",
        }, indent=2))

    force_app = metadata_dir / "force-app" / "main" / "default"
    force_app.mkdir(parents=True, exist_ok=True)


async def run(arguments: dict[str, Any]) -> str:
    """Execute the connect_customer_org tool."""
    state.set_working_dir(arguments.get("working_directory"))
    working_dir = state.get_working_dir()

    account_name = arguments.get("account_name", "").strip()
    if not account_name:
        return "STATUS: MISSING_CUSTOMER\n\naccount_name is required."

    slug = _make_slug(account_name)
    org_identifier = (arguments.get("org_identifier") or "").strip()
    default_alias = arguments.get("org_alias") or f"{slug}-sandbox"

    sf_check = sf_cli.check_sf_installed()
    if not sf_check["installed"]:
        return (
            "STATUS: SF_CLI_MISSING\n\n"
            "Salesforce CLI (sf) was not found.\n\n"
            "Install: https://developer.salesforce.com/tools/salesforcecli\n\n"
            "If already installed, ask the user to run in their terminal:\n"
            f"  sf org login web --alias {default_alias} "
            f"--instance-url {DEFAULT_LOGIN_URL}\n"
            "Then call connect_customer_org again."
        )

    # Check for existing connection
    conn_path = working_dir / "customers" / slug / "org-connection.json"
    if conn_path.exists():
        try:
            existing = json.loads(conn_path.read_text())
            existing_alias = existing.get("org_alias", "")
            if existing_alias:
                org_info = _validate_org(existing_alias)
                if org_info:
                    return (
                        "STATUS: ALREADY_CONNECTED\n\n"
                        f"- **Alias:** {existing_alias}\n"
                        f"- **Username:** {existing.get('username', '?')}\n"
                        f"- **Instance:** {existing.get('instance_url', '?')}\n"
                        f"- **Connected at:** {existing.get('connected_at', '?')}\n"
                        f"- **CS packages:** {len(existing.get('installed_packages', []))}\n\n"
                        "To reconnect with a different org, delete "
                        f"`customers/{slug}/org-connection.json` first."
                    )
        except (json.JSONDecodeError, OSError):
            pass

    report: list[str] = []
    org_info: dict[str, Any] | None = None
    resolved_alias = default_alias
    login_url = DEFAULT_LOGIN_URL

    # Resolve org identifier
    if org_identifier:
        id_type = _detect_identifier_type(org_identifier)
        report.append(f"Resolving org identifier (detected: {id_type})...\n")

        if id_type == "org_id":
            found = _resolve_by_org_id(org_identifier)
            if found:
                resolved_alias = found.get("alias") or found.get("username", default_alias)
                org_info = _validate_org(resolved_alias)
                report.append(f"Found org by ID: alias=`{resolved_alias}`\n")

        elif id_type == "url":
            login_url_parsed = _extract_instance_url(org_identifier)
            found = _resolve_by_url(org_identifier)
            if found:
                resolved_alias = found.get("alias") or found.get("username", default_alias)
                org_info = _validate_org(resolved_alias)
                report.append(f"Found org by URL: alias=`{resolved_alias}`\n")
            else:
                login_url = login_url_parsed
                report.append(f"Org not found locally. Will use URL for login.\n")

        else:
            org_info = _resolve_by_alias(org_identifier)
            if org_info:
                resolved_alias = org_identifier
                if not org_info.get("instanceUrl"):
                    org_info = _validate_org(resolved_alias)
                report.append(f"Found org by alias: `{resolved_alias}`\n")
    else:
        org_info = _resolve_by_alias(default_alias)
        if org_info:
            resolved_alias = default_alias
            if not org_info.get("instanceUrl"):
                org_info = _validate_org(resolved_alias)
            report.append(f"Found existing org under default alias `{default_alias}`\n")

    # Browser login if not resolved
    if not org_info:
        report.append("No authorized org found. Opening browser login...\n")
        error_msg = _try_browser_login(resolved_alias, login_url)
        if error_msg:
            return error_msg
        org_info = _validate_org(resolved_alias)
        if not org_info:
            return (
                "STATUS: LOGIN_VALIDATION_FAILED\n\n"
                "Login appeared to succeed but the org could not be verified.\n"
                "Call connect_customer_org again to retry."
            )
        report.append("Login successful.\n")

    # Sandbox gate
    username = org_info.get("username", "unknown")
    org_id = org_info.get("id") or org_info.get("orgId", "unknown")
    instance_url = org_info.get("instanceUrl", "unknown")

    if not _is_sandbox(org_info):
        return (
            "STATUS: PRODUCTION_BLOCKED\n\n"
            f"**BLOCKED:** The org `{username}` at `{instance_url}` "
            f"is a PRODUCTION org, not a sandbox.\n\n"
            "Customer metadata retrieval MUST target a sandbox. This is a "
            "non-negotiable safety rule.\n\n"
            "Ask the user for a sandbox URL, alias, or org ID instead.\n"
            "Sandbox URLs typically contain '--' and '.sandbox.' "
            "(e.g. https://customer--uat.sandbox.my.salesforce.com)."
        )

    report.append(f"Sandbox verified: `{username}` at `{instance_url}`\n")

    # List installed packages
    try:
        all_packages = sf_cli.installed_packages(resolved_alias)
    except sf_cli.SfCliError as e:
        logger.warning("Could not list installed packages: %s", e)
        all_packages = []

    cs_packages = _match_cs_packages(all_packages)

    if not cs_packages:
        report.append(
            "\n**WARNING:** No CloudSense packages found in this sandbox.\n"
            f"Total packages installed: {len(all_packages)}\n"
        )
    else:
        report.append(
            f"\nFound **{len(cs_packages)}** CloudSense packages "
            f"(out of {len(all_packages)} total):\n"
        )
        for pkg in cs_packages:
            report.append(f"  - {pkg['name']} ({pkg['namespace']}) v{pkg['version']}")

    # Compare with production
    comparison = _compare_with_production(cs_packages, working_dir, slug)
    comparison_report: list[str] = []

    if comparison:
        report.append("\n### Production vs Sandbox Comparison\n")
        behind = comparison.get("sandbox_behind", 0)
        missing = comparison.get("missing_in_sandbox", 0)
        ahead = comparison.get("sandbox_ahead", 0)
        matching = comparison.get("matching", 0)

        report.append(f"- Matching: **{matching}**")
        if behind:
            report.append(f"- Sandbox behind production: **{behind}**")
        if ahead:
            report.append(f"- Sandbox ahead (upgrade testing): **{ahead}**")
        if missing:
            report.append(f"- Missing in sandbox: **{missing}**")

        if behind or missing:
            report.append(
                "\n**WARNING:** This sandbox may not fully represent production. "
            )
            if behind:
                report.append(
                    f"{behind} package(s) are on older versions than production. "
                )
            if missing:
                report.append(
                    f"{missing} package(s) from production are not in this sandbox. "
                )
            report.append(
                "Code or customizations added after the last sandbox refresh "
                "won't be captured.\n"
            )

        for detail in comparison.get("details", []):
            if detail["status"] not in ("MATCHING",):
                comparison_report.append(
                    f"  - **{detail['namespace']}** ({detail['package_name']}): "
                    f"prod={detail.get('production_version', '?')} / "
                    f"sandbox={detail.get('sandbox_version', '?')} "
                    f"[{detail['status']}]"
                )
        if comparison_report:
            report.extend(comparison_report)

    # Create SFDX project scaffold
    metadata_dir = working_dir / "customers" / slug / "metadata"
    _create_sfdx_project(metadata_dir)

    # Save org-connection.json
    connection_data: dict[str, Any] = {
        "org_alias": resolved_alias,
        "org_id": org_id,
        "username": username,
        "instance_url": instance_url,
        "is_sandbox": True,
        "connected_at": datetime.now(timezone.utc).isoformat(),
        "account_name": account_name,
        "installed_packages": cs_packages,
        "total_packages_in_org": len(all_packages),
    }
    if comparison:
        connection_data["package_comparison"] = comparison

    customer_dir = working_dir / "customers" / slug
    customer_dir.mkdir(parents=True, exist_ok=True)
    conn_path.write_text(json.dumps(connection_data, indent=2))

    report.append(f"\nSaved: `customers/{slug}/org-connection.json`")
    report.append(f"SFDX project: `customers/{slug}/metadata/`")

    # Summary
    status_label = "CONNECTED"
    if not cs_packages:
        status_label = "NO_CS_PACKAGES"

    header = f"## STATUS: {status_label}\n\n"
    header += f"- **Account:** {account_name}\n"
    header += f"- **Alias:** {resolved_alias}\n"
    header += f"- **Username:** {username}\n"
    header += f"- **Org ID:** {org_id}\n"
    header += f"- **Instance:** {instance_url}\n"
    header += f"- **CS Packages:** {len(cs_packages)}\n"

    return header + "\n" + "\n".join(report)
