"""load_compass_data -- Fetch PE reference data and build the local cache.

Runs 3 SOQL queries against the LMA/PE org (cs-lma):
  1. Seasonal_Release__c (23 rows)
  2. Package__c (65 rows)
  3. Package_Version__c (~1,300 rows)

Merges the DEFAULT_NAMESPACE_MAPPING constant with live PE Package__c
data to resolve PE record IDs. Writes reference-data.json and
cache-meta.json to .cache/.

The AI calls this only when check_compass_data returns STALE or MISSING.
Requires connect_lma to have been called first.
"""

import json
import logging
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from ..utils import sf_cli, state
from ..utils.pe_helpers import (
    CACHE_DIR,
    DEFAULT_NAMESPACE_MAPPING,
    PRE_R33_POSITION,
    PRE_R33_RELEASE_ORDER,
    REFERENCE_DATA_FILE,
)

logger = logging.getLogger(__name__)

CACHE_META_FILE = "cache-meta.json"
DEFAULT_TTL_HOURS = 24

TOOL_DEFINITION = {
    "name": "load_compass_data",
    "description": (
        "Load CloudSense package and release reference data from the LMA/PE org "
        "and cache it locally. Call this when check_compass_data reports STALE or "
        "MISSING. Fetches seasonal releases, package registry, curated versions, "
        "and builds the namespace mapping. Requires connect_lma first."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "working_directory": {
                "type": "string",
                "description": "Workspace directory. The AI passes this automatically.",
            },
        },
        "required": [],
    },
}


def _query_seasonal_releases(lma_alias: str) -> list[dict[str, Any]]:
    """Query all Seasonal_Release__c records with CreatedDate for ordering."""
    query = "SELECT Id, Name, CreatedDate FROM Seasonal_Release__c"
    return sf_cli.lma_query(query, lma_alias)


def _query_pe_packages(lma_alias: str) -> list[dict[str, Any]]:
    """Query all Package__c records."""
    query = "SELECT Id, Name, Namespace__c FROM Package__c"
    return sf_cli.lma_query(query, lma_alias)


def _query_pe_versions(lma_alias: str) -> list[dict[str, Any]]:
    """Query all Package_Version__c records."""
    query = (
        "SELECT Package__c, Package_Version_No__c, "
        "Seasonal_Release__r.Name, Patch_Release__c, "
        "Withdrawn__c, Produced_For__c "
        "FROM Package_Version__c"
    )
    return sf_cli.lma_query(query, lma_alias)


def _build_seasonal_releases(
    records: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], list[str]]:
    """Build ordered seasonal release list using hybrid approach.

    Pre-R33 releases (bulk-loaded with unreliable CreatedDate) use
    hardcoded PRE_R33_RELEASE_ORDER. R33+ releases (created individually
    with reliable timestamps) are sorted by CreatedDate, so new releases
    like R38 are discovered and positioned automatically.
    """
    warnings: list[str] = []

    by_name: dict[str, dict[str, Any]] = {}
    for rec in records:
        name = rec.get("Name")
        if name:
            by_name[name] = rec

    pre_r33: list[dict[str, Any]] = []
    for name in PRE_R33_RELEASE_ORDER:
        if name in by_name:
            pre_r33.append({
                "name": name,
                "position": PRE_R33_POSITION[name],
                "id": by_name[name].get("Id"),
            })
        else:
            warnings.append(
                f"Pre-R33 release '{name}' is in PRE_R33_RELEASE_ORDER "
                f"but not found in org."
            )

    post_r33_records = []
    for name, rec in by_name.items():
        if name not in PRE_R33_POSITION:
            post_r33_records.append(rec)

    post_r33_records.sort(key=lambda r: r.get("CreatedDate", ""))

    next_pos = len(PRE_R33_RELEASE_ORDER)
    post_r33: list[dict[str, Any]] = []
    for rec in post_r33_records:
        name = rec.get("Name", "")
        post_r33.append({
            "name": name,
            "position": next_pos,
            "id": rec.get("Id"),
        })
        next_pos += 1

    releases = pre_r33 + post_r33
    return releases, warnings


def _build_packages(
    pe_records: list[dict[str, Any]],
) -> tuple[dict[str, dict[str, Any]], list[str]]:
    """Merge DEFAULT_NAMESPACE_MAPPING with live PE Package__c data.

    Returns (packages_dict, warnings) where packages_dict is keyed by namespace.
    """
    warnings: list[str] = []

    ns_to_mapping = {
        entry["namespace"]: entry for entry in DEFAULT_NAMESPACE_MAPPING
    }

    pe_by_namespace: dict[str, dict[str, Any]] = {}
    pe_no_namespace: list[dict[str, Any]] = []
    for rec in pe_records:
        ns = rec.get("Namespace__c")
        if ns and ns.lower() != "none":
            pe_by_namespace[ns] = rec
        else:
            pe_no_namespace.append(rec)

    packages: dict[str, dict[str, Any]] = {}

    for ns, mapping in ns_to_mapping.items():
        pe_rec = pe_by_namespace.get(ns)
        if pe_rec:
            packages[ns] = {
                "pe_id": pe_rec.get("Id"),
                "pe_name": mapping["pe_name"],
                "lma_name": mapping["lma_name"],
            }
        else:
            warnings.append(
                f"Namespace '{ns}' ({mapping['lma_name']}) is in "
                f"DEFAULT_NAMESPACE_MAPPING but has no matching PE Package__c record."
            )
            packages[ns] = {
                "pe_id": None,
                "pe_name": mapping["pe_name"],
                "lma_name": mapping["lma_name"],
            }

    mapped_namespaces = set(ns_to_mapping.keys())
    for ns, rec in pe_by_namespace.items():
        if ns not in mapped_namespaces:
            warnings.append(
                f"PE Package__c '{rec.get('Name')}' (namespace: {ns}) "
                f"is not in DEFAULT_NAMESPACE_MAPPING. It won't have "
                f"version currency data until the mapping is updated."
            )

    for rec in pe_no_namespace:
        pe_id = rec.get("Id")
        pe_name = rec.get("Name", "Unknown")
        raw_ns = rec.get("Namespace__c")
        synthetic_key = f"_unpackaged_{pe_id}"
        packages[synthetic_key] = {
            "pe_id": pe_id,
            "pe_name": pe_name,
            "lma_name": pe_name,
        }
        ns_label = f'"{raw_ns}"' if raw_ns else "NULL"
        warnings.append(
            f"PE Package__c '{pe_name}' has namespace {ns_label}. "
            f"Tracked under synthetic key '{synthetic_key}'."
        )

    return packages, warnings


def _build_versions(
    version_records: list[dict[str, Any]],
    packages: dict[str, dict[str, Any]],
    sr_lookup: dict[str, int],
) -> tuple[dict[str, list[dict[str, Any]]], list[str]]:
    """Group PE versions by namespace, enriched with sr_position.

    Returns (versions_dict, warnings).
    """
    warnings: list[str] = []

    pe_id_to_ns: dict[str, str] = {}
    for ns, pkg in packages.items():
        pe_id = pkg.get("pe_id")
        if pe_id:
            pe_id_to_ns[pe_id] = ns

    versions: dict[str, list[dict[str, Any]]] = defaultdict(list)
    dropped_count = 0
    dropped_by_pkg: dict[str, int] = defaultdict(int)

    for rec in version_records:
        pkg_id = rec.get("Package__c")
        if not pkg_id:
            continue

        ns = pe_id_to_ns.get(pkg_id)
        if not ns:
            dropped_count += 1
            dropped_by_pkg[pkg_id] += 1
            continue

        sr_rel = rec.get("Seasonal_Release__r")
        sr_name = sr_rel.get("Name") if isinstance(sr_rel, dict) else None
        sr_pos = sr_lookup.get(sr_name, -1) if sr_name else -1

        version_no = rec.get("Package_Version_No__c") or ""

        versions[ns].append({
            "version": version_no,
            "seasonal_release": sr_name or "Unknown",
            "sr_position": sr_pos,
            "patch": bool(rec.get("Patch_Release__c")),
            "withdrawn": bool(rec.get("Withdrawn__c")),
        })

    if dropped_count:
        warnings.append(
            f"{dropped_count} version records dropped across "
            f"{len(dropped_by_pkg)} Package__c IDs not resolved to a namespace."
        )

    for ns in versions:
        versions[ns].sort(key=lambda v: (v.get("sr_position", -1), v.get("version", "")))

    return dict(versions), warnings


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

    current_state = state.load()
    lma_alias = current_state.get("lma_org_alias")

    if not lma_alias:
        return (
            "STATUS: NOT_CONNECTED\n\n"
            "No LMA org connection found. Call connect_lma first, "
            "then retry load_compass_data."
        )

    all_warnings: list[str] = []

    try:
        sr_records = _query_seasonal_releases(lma_alias)
    except sf_cli.SfCliError as e:
        return (
            f"STATUS: QUERY_FAILED\n\n"
            f"Seasonal_Release__c query failed: {e}\n\n"
            f"This may indicate the org is not a PE or the session expired. "
            f"Try calling connect_lma again."
        )

    seasonal_releases, sr_warnings = _build_seasonal_releases(sr_records)
    all_warnings.extend(sr_warnings)

    sr_lookup: dict[str, int] = {sr["name"]: sr["position"] for sr in seasonal_releases}

    try:
        pkg_records = _query_pe_packages(lma_alias)
    except sf_cli.SfCliError as e:
        return (
            f"STATUS: QUERY_FAILED\n\n"
            f"Package__c query failed: {e}\n\n"
            f"This may indicate the org is not a PE or the session expired. "
            f"Try calling connect_lma again."
        )

    packages, pkg_warnings = _build_packages(pkg_records)
    all_warnings.extend(pkg_warnings)

    try:
        ver_records = _query_pe_versions(lma_alias)
    except sf_cli.SfCliError as e:
        return (
            f"STATUS: QUERY_FAILED\n\n"
            f"Package_Version__c query failed: {e}\n\n"
            f"This may indicate the org is not a PE or the session expired. "
            f"Try calling connect_lma again."
        )

    versions, ver_warnings = _build_versions(ver_records, packages, sr_lookup)
    all_warnings.extend(ver_warnings)

    cache_dir = working_dir / CACHE_DIR
    cache_dir.mkdir(parents=True, exist_ok=True)

    reference_data = {
        "seasonal_releases": seasonal_releases,
        "packages": {
            ns: pkg_info for ns, pkg_info in packages.items()
        },
        "versions": versions,
    }

    ref_path = cache_dir / REFERENCE_DATA_FILE
    ref_path.write_text(json.dumps(reference_data, indent=2))

    now_iso = datetime.now(timezone.utc).isoformat()
    cache_meta = {
        "cached_at": now_iso,
        "ttl_hours": DEFAULT_TTL_HOURS,
        "org_alias": lma_alias,
    }
    meta_path = cache_dir / CACHE_META_FILE
    meta_path.write_text(json.dumps(cache_meta, indent=2))

    mapped_count = sum(1 for p in packages.values() if p.get("pe_id"))
    unmapped_count = sum(1 for p in packages.values() if not p.get("pe_id"))
    total_versions = sum(len(vlist) for vlist in versions.values())

    summary_parts = [
        "STATUS: READY\n",
        "Reference data cached successfully.",
        f"- Seasonal releases: {len(seasonal_releases)}",
        f"- Packages: {mapped_count} mapped"
        + (f" ({unmapped_count} without PE match)" if unmapped_count else ""),
        f"- Curated versions: {total_versions:,}",
        f"- Cache location: {CACHE_DIR}/{REFERENCE_DATA_FILE}",
        f"- Valid for: {DEFAULT_TTL_HOURS} hours",
        "",
        "This data is used by get_customer_licenses, get_portfolio_data,",
        "and get_upgrade_path for accurate version currency reporting.",
    ]

    if all_warnings:
        summary_parts.append("")
        summary_parts.append("WARNINGS:")
        for w in all_warnings:
            summary_parts.append(f"- {w}")

    return "\n".join(summary_parts)
