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

This tool:
1. Queries all installed packages via sf CLI
2. Identifies CloudSense packages by namespace
3. Flags packages with cloud service dependencies
4. Writes installed-packages.json to the analysis directory
5. Updates the state file with package count and details
"""

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

from ..utils import sf_cli, state, paths

logger = logging.getLogger(__name__)

KNOWN_CS_NAMESPACES: set[str] = {
    "csutil", "csordtelcoa", "cspmb", "cfgug1", "cscfglint",
    "cscrm", "CSPOFA", "cscfga", "CSCAP", "CSSX", "csord",
    "cspduia", "csog", "csexpimp1", "csordcb", "csbb",
    "csdocgen", "csclmdg", "csconta", "csam", "csslm",
    "cscfgc", "csdg",
}

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

TOOL_DEFINITION = {
    "name": "get_installed_packages",
    "description": (
        "Discover all installed packages in the Salesforce org and identify "
        "CloudSense packages. Flags packages with cloud service dependencies. "
        "Writes installed-packages.json to the analysis directory. "
        "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 _classify_package(pkg: dict[str, Any]) -> dict[str, Any]:
    """Classify a single package and build a structured record."""
    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 ""

    is_cs = namespace in KNOWN_CS_NAMESPACES or namespace.lower() in {
        ns.lower() for ns in KNOWN_CS_NAMESPACES
    }

    cloud_service = CLOUD_SERVICE_PACKAGES.get(namespace)

    return {
        "name": name,
        "namespace": namespace,
        "version": version,
        "version_id": version_id,
        "version_name": version_name,
        "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} 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 packages ──────────────────────────────────────
    classified = [_classify_package(p) for p in raw_packages]
    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"Found **{len(classified)}** installed packages "
        f"(**{len(cs_packages)}** CloudSense, "
        f"**{len(non_cs_packages)}** non-CloudSense).\n"
    )

    # ── 4. CloudSense packages table ──────────────────────────────
    report.append("### CloudSense Packages\n")
    report.append(
        "| # | Package | Namespace | Version | Cloud Service |"
    )
    report.append(
        "|---|---------|-----------|---------|---------------|"
    )
    for i, pkg in enumerate(cs_packages, 1):
        cloud_svc = pkg["cloud_service_name"] or "-"
        report.append(
            f"| {i} | {pkg['name']} | {pkg['namespace']} | "
            f"{pkg['version']} | {cloud_svc} |"
        )

    # ── 5. Non-CS packages (informational) ────────────────────────
    if non_cs_packages:
        report.append("\n### Non-CloudSense Packages (excluded from analysis)\n")
        for pkg in non_cs_packages:
            report.append(
                f"- {pkg['name']} ({pkg['namespace']}) v{pkg['version']}"
            )

    # ── 6. Cloud service warnings ─────────────────────────────────
    if cloud_service_packages:
        report.append("\n### Cloud Service Warnings\n")
        for pkg in cloud_service_packages:
            warnings.append(
                f"{pkg['namespace']} ({pkg['name']}) has a corresponding "
                f"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 file ──────────────────────────────────────
    output_dir = paths.ensure_dir(working_dir / "analysis")
    output_file = output_dir / "installed-packages.json"
    output_file.write_text(json.dumps(classified, indent=2))
    report.append(f"\n### Output\n")
    report.append(f"- Full package list: `{output_file}`")

    cs_output_file = output_dir / "cloudsense-packages.json"
    cs_output_file.write_text(json.dumps(cs_packages, indent=2))
    report.append(f"- CloudSense only: `{cs_output_file}`")

    # ── 8. Update state ───────────────────────────────────────────
    current_state = state.load_state(working_dir)
    if current_state:
        current_state["calibration"]["packages"] = len(cs_packages)
    state.mark_step_completed(working_dir, "get_packages")
    state.update_state(
        working_dir,
        packages_discovered={
            "total": len(classified),
            "cloudsense": len(cs_packages),
            "cloud_service_flagged": len(cloud_service_packages),
            "namespaces": [p["namespace"] for p in cs_packages],
        },
    )

    # ── 9. Summary ────────────────────────────────────────────────
    report.append("\n---\n")
    report.append("## STATUS: READY\n")
    report.append(
        f"Discovered {len(cs_packages)} CloudSense packages "
        f"for upgrade assessment.\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)
