"""get_installed_packages tool -- builds the package baseline.

This tool builds the authoritative installed-packages.json from the
CloudSense License Management Portal production data. In both modes
(sandbox connected or portal-only), the baseline comes from LMA:

1. Loads lma_customer_data.packages from state (set by get_customer_licenses)
2. Classifies each package as CloudSense or not via namespace/name heuristics
3. Flags packages with cloud service dependencies
4. If a sandbox org is connected, cross-references sandbox versions and flags drift
5. Writes installed-packages.json for downstream tools (get_repo_mapping, etc.)
6. Updates state with package counts and details
"""

import json
import logging
import re
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": (
        "Build the package baseline from the CloudSense License Management "
        "Portal production data. Always uses license data as the authoritative "
        "source. If a sandbox org is connected, cross-references sandbox "
        "versions and flags drift. Writes installed-packages.json for "
        "downstream tools. Must be called AFTER init_assessment."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "org_alias": {
                "type": "string",
                "description": (
                    "Salesforce sandbox org alias for cross-referencing. "
                    "Optional -- reads from state if omitted. Not needed "
                    "in portal-only mode."
                ),
            },
            "customer_name": {
                "type": "string",
                "description": (
                    "Customer name. Reads from state if omitted."
                ),
            },
            "working_directory": {
                "type": "string",
                "description": (
                    "Workspace directory for state and output files. "
                    "The AI passes this automatically."
                ),
            },
        },
        "required": [],
    },
}


def _is_likely_cloudsense(namespace: str, name: str) -> bool:
    """Heuristic: does this package look like a CloudSense package?"""
    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_lma_package(pkg: dict[str, Any]) -> dict[str, Any]:
    """Build a structured record from an LMA package entry in state."""
    namespace = pkg.get("namespace") or ""
    name = pkg.get("name") or ""
    version = pkg.get("version") 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,
        "status": pkg.get("status", ""),
        "package_id": pkg.get("package_id", ""),
        "subscriber_package_id": pkg.get("subscriber_package_id", ""),
        "installed_version_name": pkg.get("installed_version_name", ""),
        "latest_version": pkg.get("latest_version", ""),
        "latest_version_name": pkg.get("latest_version_name", ""),
        "latest_release_date": pkg.get("latest_release_date", ""),
        "source": "lma",
        "is_cloudsense": is_cs,
        "has_cloud_service": cloud_service is not None,
        "cloud_service_name": cloud_service,
    }


def _classify_sandbox_package(pkg: dict[str, Any]) -> dict[str, Any]:
    """Build a structured record from a raw sf CLI package entry."""
    namespace = pkg.get("SubscriberPackageNamespace") or ""
    name = pkg.get("SubscriberPackageName") or ""
    version = pkg.get("SubscriberPackageVersionNumber") or ""

    return {
        "name": name,
        "namespace": namespace,
        "version": version,
    }


def _cross_reference_sandbox(
    classified: list[dict[str, Any]],
    sandbox_packages: list[dict[str, Any]],
    report: list[str],
) -> list[str]:
    """Cross-reference LMA baseline against sandbox packages.

    Adds sandbox_version and version_drift fields to classified records.
    Returns a list of drift warnings.
    """
    sandbox_by_ns: dict[str, dict[str, Any]] = {}
    for sp in sandbox_packages:
        ns = (sp["namespace"] or "").lower()
        if ns:
            sandbox_by_ns[ns] = sp

    drift_warnings: list[str] = []
    drifted = 0
    matched = 0
    missing_from_sandbox = 0

    for pkg in classified:
        ns = (pkg["namespace"] or "").lower()
        if not ns:
            continue

        sandbox_pkg = sandbox_by_ns.get(ns)
        if sandbox_pkg:
            matched += 1
            pkg["sandbox_version"] = sandbox_pkg["version"]
            if pkg["version"] != sandbox_pkg["version"]:
                drifted += 1
                pkg["version_drift"] = True
                drift_warnings.append(
                    f"{pkg['namespace']} ({pkg['name']}): production "
                    f"v{pkg['version']} vs sandbox v{sandbox_pkg['version']}"
                )
            else:
                pkg["version_drift"] = False
        else:
            if pkg.get("is_cloudsense"):
                missing_from_sandbox += 1
                pkg["sandbox_version"] = None
                pkg["version_drift"] = None

    report.append(f"\n### Sandbox Cross-Reference\n")
    report.append(f"- Matched: {matched} packages")
    report.append(f"- Version drift: {drifted} packages")
    report.append(f"- Missing from sandbox: {missing_from_sandbox} CS packages")

    return drift_warnings


async def run(arguments: dict[str, Any]) -> str:
    """Execute the get_installed_packages tool."""
    state.set_working_dir(arguments.get("working_directory"))
    working_dir = state.get_working_dir()
    existing_state = state.load_state() or {}

    customer = (
        arguments.get("customer_name")
        or existing_state.get("customer", "")
    )
    org_alias = (
        arguments.get("org_alias")
        or existing_state.get("org_alias", "")
    )
    assessment_mode = existing_state.get("assessment_mode", "")
    is_lma_only = assessment_mode == "lma_only"

    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. Load LMA package data from state ───────────────────────
    lma_customer_data = existing_state.get("lma_customer_data", {})
    lma_packages = lma_customer_data.get("packages", [])

    if not lma_packages:
        return (
            "## BLOCKER: No license data in state\n\n"
            "The License Portal package data is missing from state. "
            "Run get_customer_licenses first to populate it."
        )

    # ── 2. Resume check ──────────────────────────────────────────
    if state.is_step_completed(working_dir, "get_packages"):
        output_file = working_dir / "analysis" / "installed-packages.json"
        if output_file.exists():
            prev = json.loads(output_file.read_text())
            cs_count = sum(1 for p in prev if p.get("is_cloudsense"))
            return (
                "## Step already completed\n\n"
                f"Package discovery was already run. Found {len(prev)} "
                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")

    # ── 3. Build baseline from LMA production data ────────────────
    report.append("## Package Discovery (from License Portal)\n")

    classified: list[dict[str, Any]] = []
    for pkg in lma_packages:
        try:
            classified.append(_classify_lma_package(pkg))
        except Exception as exc:
            logger.warning("Skipping unreadable package entry %r: %s", pkg, 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"Built package baseline from License Portal -- "
        f"**{len(classified)}** packages total.\n"
    )

    # ── 4. ALL packages table ─────────────────────────────────────
    report.append("### All Installed Packages\n")
    report.append(
        "| # | Package Name | Namespace | Production Version | 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']}** | {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 "
            f"packages that should be included in the assessment.\n"
        )

    # ── 6. Sandbox cross-reference (if available) ─────────────────
    if not is_lma_only and org_alias:
        report.append("### Sandbox Query\n")
        project_dir = working_dir / customer if customer else working_dir
        try:
            raw_sandbox = sf_cli.list_installed_packages(
                org_alias, cwd=project_dir
            )
            sandbox_classified = [
                _classify_sandbox_package(p)
                for p in raw_sandbox
                if isinstance(p, dict)
            ]
            report.append(
                f"Queried sandbox **{org_alias}** -- "
                f"**{len(sandbox_classified)}** packages.\n"
            )
            drift_warnings = _cross_reference_sandbox(
                classified, sandbox_classified, report
            )
            warnings.extend(drift_warnings)
        except sf_cli.SfCliError as e:
            report.append(
                f"Sandbox query failed: {e}\n"
                "Proceeding with License Portal data only.\n"
            )
    elif is_lma_only:
        report.append(
            "### Portal-Only Mode\n"
            "Sandbox cross-reference skipped (portal-only assessment).\n"
        )

    # ── 7. 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']} "
                f"has a corresponding cloud service: "
                f"**{pkg['cloud_service_name']}**. Verify that the cloud "
                f"service version aligns with the target upgrade version. "
                f"Contact CloudSense CSM for the compatibility matrix."
            )
            report.append(f"- {warnings[-1]}")

    # ── 8. 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}`"
    )

    # ── 9. 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
            ],
        },
    )

    # ── 10. Summary ───────────────────────────────────────────────
    report.append("\n---\n")
    report.append("## STATUS: READY\n")
    report.append(
        f"Built package baseline with **{len(classified)}** packages "
        f"(**{len(cs_packages)}** likely CloudSense) from production "
        f"license data.\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)
