"""get_installed_packages tool -- discovers all packages in the org.

This tool:
1. Queries ALL installed packages via sf CLI (no filtering, no skipping)
2. Shows every package with its current version
3. Identifies likely CloudSense packages via namespace AND name heuristics
4. Flags packages with cloud service dependencies
5. Writes installed-packages.json with ALL raw fields for downstream use
6. Updates the state file with package count and details
"""

import json
import logging
import re
from pathlib import Path
from typing import Any

from ..utils import sf_cli, state, paths

logger = logging.getLogger(__name__)

CLOUD_SERVICE_PACKAGES: dict[str, str] = {
    "cspofa": "Orchestrator Cloud Service",
    "cscfga": "Configurator Cloud Service",
    "csslm": "Solution Management Cloud Service",
}

CS_NAMESPACE_PATTERNS: list[re.Pattern] = [
    re.compile(r"^cs", re.IGNORECASE),
    re.compile(r"^cf", re.IGNORECASE),
    re.compile(r"^cpra", re.IGNORECASE),
]

CS_NAME_KEYWORDS: list[str] = [
    "cloudsense",
    "product basket",
    "e-commerce telco",
    "solution rules",
    "cross product rules",
]

NON_CS_NAMESPACES: set[str] = {
    "sf_com_apps", "sf_chttr_apps", "sfadminapps",
    "siqcloud", "sfga",
}

TOOL_DEFINITION = {
    "name": "get_installed_packages",
    "description": (
        "Discover ALL installed packages in the Salesforce org with their "
        "current versions, names, namespaces, and IDs. "
        "Identifies likely CloudSense packages via namespace and name heuristics. "
        "Flags packages with cloud service dependencies. "
        "Writes installed-packages.json with all details for downstream tools. "
        "Must be called AFTER init_assessment completes successfully."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "working_directory": {
                "type": "string",
                "description": (
                    "Absolute path to the assessment working directory "
                    "(same value used in init_assessment)."
                ),
            },
            "org_alias": {
                "type": "string",
                "description": (
                    "Salesforce org alias to query. Must be an authorized org."
                ),
            },
            "customer_name": {
                "type": "string",
                "description": (
                    "Customer name (same value used in init_assessment). "
                    "Used to locate the SFDX project directory."
                ),
            },
        },
        "required": ["working_directory", "org_alias", "customer_name"],
    },
}


def _is_likely_cloudsense(namespace: str, name: str) -> bool:
    """Heuristic: does this package look like a CloudSense package?

    Checks:
    1. Namespace starts with cs/cf/cpra (case-insensitive)
    2. Package name contains CloudSense-related keywords
    3. Excludes known Salesforce platform namespaces
    """
    ns_lower = (namespace or "").lower()

    if ns_lower and ns_lower in NON_CS_NAMESPACES:
        return False

    if namespace and any(pat.match(namespace) for pat in CS_NAMESPACE_PATTERNS):
        return True

    name_lower = (name or "").lower()
    if any(kw in name_lower for kw in CS_NAME_KEYWORDS):
        return True

    return False


def _classify_package(pkg: dict[str, Any]) -> dict[str, Any]:
    """Build a structured record from a raw sf CLI package entry.

    Preserves ALL raw fields and adds classification metadata.
    """
    namespace = pkg.get("SubscriberPackageNamespace") or ""
    name = pkg.get("SubscriberPackageName") or ""
    version = pkg.get("SubscriberPackageVersionNumber") or ""
    version_id = pkg.get("SubscriberPackageVersionId") or ""
    version_name = pkg.get("SubscriberPackageVersionName") or ""
    package_id = pkg.get("SubscriberPackageId") or ""
    record_id = pkg.get("Id") or ""

    is_cs = _is_likely_cloudsense(namespace, name)
    cloud_service = CLOUD_SERVICE_PACKAGES.get(namespace.lower()) if namespace else None

    return {
        "name": name,
        "namespace": namespace,
        "version": version,
        "version_name": version_name,
        "version_id": version_id,
        "package_id": package_id,
        "record_id": record_id,
        "is_cloudsense": is_cs,
        "has_cloud_service": cloud_service is not None,
        "cloud_service_name": cloud_service,
    }


async def run(arguments: dict[str, Any]) -> str:
    """Execute the get_installed_packages tool."""
    working_dir = Path(arguments["working_directory"])
    org_alias = arguments["org_alias"]
    customer = arguments["customer_name"]

    report: list[str] = []
    warnings: list[str] = []

    # ── 0. Verify init was completed ──────────────────────────────
    if not state.is_step_completed(working_dir, "init"):
        return (
            "## BLOCKER: init_assessment not completed\n\n"
            "You must call init_assessment first before discovering packages.\n"
            "Call init_assessment with the customer_name, working_directory, "
            "and target_version parameters."
        )

    # ── 1. Resume check ──────────────────────────────────────────
    if state.is_step_completed(working_dir, "get_packages"):
        output_file = working_dir / "analysis" / "installed-packages.json"
        if output_file.exists():
            existing = json.loads(output_file.read_text())
            cs_count = sum(1 for p in existing if p.get("is_cloudsense"))
            return (
                "## Step already completed\n\n"
                f"Package discovery was already run. Found {len(existing)} "
                f"total packages ({cs_count} likely CloudSense).\n"
                f"Output: {output_file}\n\n"
                "To re-run, manually delete the state entry or the output file "
                "and call this tool again.\n\n"
                "Next step: Ask the user if they want to assess ALL CloudSense "
                "packages or specific ones, then proceed to get_customer_metadata."
            )

    state.mark_step_started(working_dir, "get_packages")

    # ── 2. Query the org ──────────────────────────────────────────
    report.append("## Package Discovery\n")
    project_dir = working_dir / customer

    try:
        raw_packages = sf_cli.list_installed_packages(
            org_alias, cwd=project_dir
        )
    except sf_cli.SfCliError as e:
        state.mark_step_failed(working_dir, "get_packages", str(e))
        return (
            "## FAILED: Could not list installed packages\n\n"
            f"Error: {e}\n\n"
            "Possible causes:\n"
            "- Org is not authorized (re-run init_assessment)\n"
            "- Session token expired (re-authorize with sf org login web)\n"
            "- Network connectivity issue\n"
        )

    if not raw_packages:
        state.mark_step_failed(
            working_dir, "get_packages",
            "No installed packages returned from the org"
        )
        return (
            "## WARNING: No installed packages found\n\n"
            "The org returned zero installed packages. This could mean:\n"
            "- The org has no managed packages (unlikely for a CS customer)\n"
            "- The connected user lacks permissions to view packages\n"
            "- The org alias points to a different org than expected\n\n"
            f"Verify by running: sf package installed list "
            f"--target-org {org_alias} --json\n"
        )

    # ── 3. Classify ALL packages (never skip any) ─────────────────
    classified = []
    for p in raw_packages:
        try:
            classified.append(_classify_package(p if isinstance(p, dict) else {}))
        except Exception as exc:
            logger.warning("Skipping unreadable package entry %r: %s", p, exc)

    cs_packages = [p for p in classified if p["is_cloudsense"]]
    non_cs_packages = [p for p in classified if not p["is_cloudsense"]]
    cloud_service_packages = [p for p in cs_packages if p["has_cloud_service"]]

    report.append(
        f"Queried org **{org_alias}** -- found **{len(classified)}** "
        f"installed packages total.\n"
    )

    # ── 4. ALL packages table (every single one) ─────────────────
    report.append("### All Installed Packages\n")
    report.append(
        "| # | Package Name | Namespace | Current Version | Version Name | CS? |"
    )
    report.append(
        "|---|-------------|-----------|-----------------|-------------|-----|"
    )
    for i, pkg in enumerate(classified, 1):
        cs_flag = "Yes" if pkg["is_cloudsense"] else "-"
        ns_display = pkg["namespace"] or "(none)"
        report.append(
            f"| {i} | {pkg['name']} | {ns_display} | "
            f"**{pkg['version']}** | {pkg['version_name']} | {cs_flag} |"
        )

    # ── 5. Summary counts ─────────────────────────────────────────
    no_ns_count = sum(1 for p in classified if not p["namespace"])
    report.append(
        f"\n**Total:** {len(classified)} packages "
        f"({no_ns_count} without namespace prefix).\n"
        f"**Likely CloudSense:** {len(cs_packages)} packages "
        f"(detected by namespace `cs*/cf*/cpra*` or name keywords).\n"
        f"**Other:** {len(non_cs_packages)} packages.\n"
    )

    if no_ns_count > 0:
        report.append(
            f"NOTE: {no_ns_count} packages have no namespace. "
            f"Review them above -- some may be CloudSense unmanaged packages "
            f"that should be included in the assessment.\n"
        )

    # ── 6. Cloud service warnings ─────────────────────────────────
    if cloud_service_packages:
        report.append("### Cloud Service Warnings\n")
        for pkg in cloud_service_packages:
            warnings.append(
                f"{pkg['namespace']} ({pkg['name']}) v{pkg['version']} has a "
                f"corresponding cloud service: **{pkg['cloud_service_name']}**. "
                f"Verify that the cloud service version aligns with the "
                f"target upgrade version. Contact CloudSense CSM for the "
                f"compatibility matrix."
            )
            report.append(f"- {warnings[-1]}")

    # ── 7. Write output files ─────────────────────────────────────
    output_dir = paths.ensure_dir(working_dir / "analysis")

    output_file = output_dir / "installed-packages.json"
    output_file.write_text(json.dumps(classified, indent=2))

    cs_output_file = output_dir / "cloudsense-packages.json"
    cs_output_file.write_text(json.dumps(cs_packages, indent=2))

    report.append(f"\n### Output Files\n")
    report.append(f"- All packages ({len(classified)}): `{output_file}`")
    report.append(f"- CloudSense only ({len(cs_packages)}): `{cs_output_file}`")

    # ── 8. Update state ───────────────────────────────────────────
    state.mark_step_completed(working_dir, "get_packages")
    state.update_state(
        working_dir,
        packages_discovered={
            "total": len(classified),
            "cloudsense": len(cs_packages),
            "no_namespace": no_ns_count,
            "cloud_service_flagged": len(cloud_service_packages),
            "packages": [
                {
                    "name": p["name"],
                    "namespace": p["namespace"],
                    "version": p["version"],
                    "is_cloudsense": p["is_cloudsense"],
                }
                for p in classified
            ],
        },
    )

    # ── 9. Summary ────────────────────────────────────────────────
    report.append("\n---\n")
    report.append("## STATUS: READY\n")
    report.append(
        f"Discovered **{len(classified)}** installed packages "
        f"(**{len(cs_packages)}** likely CloudSense) with current versions.\n"
    )
    report.append(
        "**Next step:** Ask the user:\n"
        '"Do you want to assess ALL CloudSense packages or specific ones?"\n\n'
        "If specific: show the list above and let them choose. "
        "Warn about package dependencies -- some packages depend on others "
        "and should be upgraded together.\n\n"
        "Then proceed to `get_customer_metadata`."
    )

    if warnings:
        report.append("\n## Warnings\n")
        for w in warnings:
            report.append(f"- {w}")

    return "\n".join(report)
