"""get_portfolio_insights tool -- cross-customer license intelligence.

Queries ALL active production licenses from the LMA org in bulk,
computes deterministic scores for renewal urgency, license utilization,
version currency, and customer size, then produces a priority-ranked
portfolio report with heatmap and file artifacts.

The scoring logic lives here (not in the AI) so results are
consistent, auditable, and repeatable across conversations.
"""

import json
import logging
from collections import defaultdict
from datetime import datetime, timezone

from typing import Any

from ..utils import sf_cli, state

logger = logging.getLogger(__name__)

TOOL_DEFINITION = {
    "name": "get_portfolio_insights",
    "description": (
        "Analyze the ENTIRE CloudSense customer portfolio for renewal, "
        "upsell, and upgrade opportunities. Queries all active production "
        "licenses from the LMA org, computes priority scores using defined "
        "rules (not AI-invented), and produces a ranked report with heatmap. "
        "Use this for any cross-customer analysis: 'which customers need "
        "renewal?', 'who has upsell opportunity?', 'show me the portfolio'. "
        "Requires connect_lma to have been called first."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "min_licenses": {
                "type": "integer",
                "description": (
                    "Optional filter: only include customers whose largest "
                    "package has at least this many licenses. Default: no filter."
                ),
            },
            "min_packages": {
                "type": "integer",
                "description": (
                    "Optional filter: only include customers with at least "
                    "this many packages. Default: no filter."
                ),
            },
            "expiry_horizon_days": {
                "type": "integer",
                "description": (
                    "Only include customers with licenses expiring within "
                    "this many days. Default: 365."
                ),
            },
            "working_directory": {
                "type": "string",
                "description": (
                    "Workspace directory for state and output files. "
                    "The AI passes this automatically."
                ),
            },
        },
        "required": [],
    },
}


# ── Scoring rules (deterministic, not AI-decided) ──────────────

def _renewal_score(min_days: float | None) -> tuple[int, str]:
    """Score renewal urgency based on nearest license expiry."""
    if min_days is None:
        return 0, "OK"
    if min_days < 30:
        return 100, "CRITICAL"
    if min_days < 90:
        return 80, "URGENT"
    if min_days < 180:
        return 50, "WATCH"
    if min_days < 365:
        return 20, "PLAN"
    return 0, "OK"


def _license_util_score(max_util_pct: float) -> tuple[int, str]:
    """Score license utilization based on highest per-package usage."""
    if max_util_pct >= 100:
        return 100, "MAXED"
    if max_util_pct >= 90:
        return 80, "HIGH"
    if max_util_pct >= 75:
        return 50, "MODERATE"
    return 0, "LOW"


def _version_score(packages_behind: int) -> tuple[int, str]:
    """Score version currency based on packages not on latest."""
    if packages_behind >= 3:
        return 100, "STALE"
    if packages_behind >= 1:
        return 60, "BEHIND"
    return 0, "CURRENT"


def _size_weight(max_licenses: int, package_count: int) -> float:
    """Size multiplier based on largest per-package license count."""
    if max_licenses > 500 or package_count > 10:
        return 1.5
    if max_licenses >= 100 or package_count >= 5:
        return 1.0
    return 0.7


def _combined_priority(
    renewal: int, utilization: int, version: int, size_w: float
) -> int:
    """Compute weighted priority score (0-100)."""
    raw = (renewal * 0.35) + (utilization * 0.25) + (version * 0.20) + (20 * size_w)
    return min(100, int(round(raw)))


def _tier_label(priority: int) -> str:
    if priority >= 70:
        return "Tier 1 -- Immediate Action"
    if priority >= 40:
        return "Tier 2 -- Plan"
    return "Tier 3 -- Monitor"


# ── Helpers ─────────────────────────────────────────────────────

def _safe_float(val: Any) -> float | None:
    if val is None:
        return None
    try:
        return float(val)
    except (ValueError, TypeError):
        return None


def _safe_int(val: Any) -> int:
    try:
        return int(val)
    except (ValueError, TypeError):
        return 0


def _get_lma_alias() -> str:
    """Read LMA alias from state, or return default."""
    existing = state.load_state()
    if existing and existing.get("lma_org_alias"):
        return existing["lma_org_alias"]
    return "cs-lma"


# Full field list including custom CloudSense additions
_ALL_FIELDS = [
    "sfLma__Account__r.Name",
    "sfLma__Account__r.Id",
    "Package_Name__c",
    "Package_Version_Number__c",
    "sfLma__License_Status__c",
    "sfLma__Expiration_Date__c",
    "Days_To_Expire__c",
    "sfLma__Licensed_Seats__c",
    "sfLma__Used_Licenses__c",
    "CSLabs1__Unused_Licenses__c",
    "sfLma__Package_Version__r.sfLma__Package__c",
]

# Core sfLma fields only (no custom fields that may not exist)
_CORE_FIELDS = [
    "sfLma__Account__r.Name",
    "sfLma__Account__r.Id",
    "sfLma__License_Status__c",
    "sfLma__Expiration_Date__c",
    "sfLma__Licensed_Seats__c",
    "sfLma__Used_Licenses__c",
    "sfLma__Package_Version__r.sfLma__Package__c",
]

_ALL_WHERE = (
    "sfLma__Is_Sandbox__c = false "
    "AND sfLma__License_Status__c = 'Active' "
    "AND sfLma__Account__r.Name != null"
)


def _try_query(
    fields: list[str], where: str, lma_alias: str
) -> tuple[list[dict[str, Any]], str | None]:
    """Run a SOQL query, return (records, error_or_None)."""
    select = ", ".join(fields)
    query = f"SELECT {select} FROM sfLma__License__c WHERE {where}"
    try:
        return sf_cli.lma_query(query, lma_alias), None
    except sf_cli.SfCliError as e:
        return [], str(e)


def _bulk_query_licenses(
    lma_alias: str,
) -> tuple[list[dict[str, Any]], str | None, str]:
    """Query all active production licenses from LMA with retry.

    Strategy:
    1. Try with all fields (includes custom CloudSense fields).
    2. If that fails, retry with core sfLma-only fields.

    Returns (records, error_message_or_None, field_set_used).
    """
    records, err = _try_query(_ALL_FIELDS, _ALL_WHERE, lma_alias)
    if records:
        return records, None, "all"

    if err:
        logger.info("Full field query failed, retrying with core fields: %s", err)
        records2, err2 = _try_query(_CORE_FIELDS, _ALL_WHERE, lma_alias)
        if records2:
            return records2, None, "core"
        return [], err2 or err, "none"

    return [], None, "all"


def _query_latest_versions(
    package_ids: set[str], lma_alias: str
) -> dict[str, str]:
    """Get latest version per package in a single batched SOQL query."""
    if not package_ids:
        return {}

    ids_csv = ", ".join(f"'{pid}'" for pid in package_ids)
    query = (
        f"SELECT sfLma__Package__c, sfLma__Version_Number__c, "
        f"sfLma__Release_Date__c "
        f"FROM sfLma__Package_Version__c "
        f"WHERE sfLma__Package__c IN ({ids_csv}) "
        f"ORDER BY sfLma__Release_Date__c DESC"
    )
    try:
        records = sf_cli.lma_query(query, lma_alias)
    except sf_cli.SfCliError as e:
        logger.warning("Batch version query failed: %s", e)
        return {}

    latest: dict[str, str] = {}
    for rec in records:
        pkg_id = rec.get("sfLma__Package__c", "")
        ver = rec.get("sfLma__Version_Number__c", "")
        if pkg_id and ver and pkg_id not in latest:
            latest[pkg_id] = ver
    return latest


# ── Main ────────────────────────────────────────────────────────

async def run(arguments: dict[str, Any]) -> str:
    """Execute the get_portfolio_insights tool."""
    state.set_working_dir(arguments.get("working_directory"))
    min_licenses = arguments.get("min_licenses", 0)
    min_packages = arguments.get("min_packages", 0)
    expiry_horizon = arguments.get("expiry_horizon_days", 365)

    lma_alias = _get_lma_alias()

    report: list[str] = []
    report.append("# Portfolio Insights\n")
    report.append(f"- LMA org: {lma_alias}")
    report.append(f"- Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}")
    if min_licenses:
        report.append(f"- Filter: min {min_licenses} licenses")
    if min_packages:
        report.append(f"- Filter: min {min_packages} packages")
    report.append("")

    # ── 1. Bulk query (with automatic retry) ───────────────────
    all_licenses, query_error, field_set = _bulk_query_licenses(lma_alias)
    if not all_licenses:
        report.append("## Query Result: No Data\n")
        if query_error:
            report.append(
                f"Both full and core-field queries failed.\n\n"
                f"Error:\n```\n{query_error}\n```\n"
            )
            report.append(
                "Possible causes:\n"
                "- The sfLma managed package is not installed in this org\n"
                "- The org alias is wrong (call connect_lma with the correct org)\n"
                "- SOQL access to sfLma__License__c is restricted by profile"
            )
        else:
            report.append("No active production licenses found in LMA.")
        report.append("\n## STATUS: NO DATA\n")
        return "\n".join(report)

    if field_set == "core":
        report.append(
            "- Note: custom fields (Package_Name__c, Days_To_Expire__c, etc.) "
            "are not available. Using core sfLma fields only. Some scoring "
            "signals (version currency, renewal days) may be limited."
        )

    report.append(f"- Total active production licenses: {len(all_licenses)}")

    # ── 2. Group by customer ───────────────────────────────────
    customers: dict[str, list[dict]] = defaultdict(list)
    for lic in all_licenses:
        acct_ref = lic.get("sfLma__Account__r")
        if isinstance(acct_ref, dict):
            acct_name = acct_ref.get("Name", "Unknown")
        else:
            continue
        customers[acct_name].append(lic)

    report.append(f"- Unique customers: {len(customers)}")

    # ── 3. Collect unique package IDs for version lookup ───────
    package_ids: set[str] = set()
    for licenses in customers.values():
        for lic in licenses:
            pkg_ref = lic.get("sfLma__Package_Version__r")
            if isinstance(pkg_ref, dict):
                pid = pkg_ref.get("sfLma__Package__c", "")
                if pid:
                    package_ids.add(pid)

    report.append(f"- Unique packages: {len(package_ids)}")
    report.append("- Querying latest version for each package...")

    latest_versions = _query_latest_versions(package_ids, lma_alias)
    report.append(f"- Latest versions resolved: {len(latest_versions)}\n")

    # ── 4. Score each customer ─────────────────────────────────
    scored: list[dict[str, Any]] = []

    for acct_name, licenses in customers.items():
        acct_ref = licenses[0].get("sfLma__Account__r", {})
        acct_id = acct_ref.get("Id", "") if isinstance(acct_ref, dict) else ""

        package_count = len(set(
            l.get("Package_Name__c", "") for l in licenses
        ))

        # Max licenses on any single package (not summed across packages)
        per_pkg_licenses = [
            _safe_int(l.get("sfLma__Licensed_Seats__c"))
            for l in licenses
        ]
        max_licenses = max(per_pkg_licenses) if per_pkg_licenses else 0

        if max_licenses < min_licenses or package_count < min_packages:
            continue

        # Renewal
        days_values = [_safe_float(l.get("Days_To_Expire__c")) for l in licenses]
        days_values = [d for d in days_values if d is not None]
        min_days = min(days_values) if days_values else None

        renewal_sc, renewal_label = _renewal_score(min_days)

        # License utilization (per-package)
        max_util = 0.0
        high_util_packages = 0
        for lic in licenses:
            licensed = _safe_float(lic.get("sfLma__Licensed_Seats__c"))
            used = _safe_float(lic.get("sfLma__Used_Licenses__c"))
            if licensed and licensed > 0 and used is not None:
                util = (used / licensed) * 100
                max_util = max(max_util, util)
                if util >= 90:
                    high_util_packages += 1

        util_sc, util_label = _license_util_score(max_util)

        # Version
        packages_behind = 0
        for lic in licenses:
            installed = lic.get("Package_Version_Number__c", "")
            pkg_ref = lic.get("sfLma__Package_Version__r")
            if isinstance(pkg_ref, dict):
                pid = pkg_ref.get("sfLma__Package__c", "")
                latest = latest_versions.get(pid, "")
                if latest and installed and latest != installed:
                    packages_behind += 1

        version_sc, version_label = _version_score(packages_behind)

        # Size & priority
        size_w = _size_weight(max_licenses, package_count)
        priority = _combined_priority(renewal_sc, util_sc, version_sc, size_w)
        tier = _tier_label(priority)

        scored.append({
            "customer": acct_name,
            "account_id": acct_id,
            "package_count": package_count,
            "max_licenses": max_licenses,
            "min_days_to_expiry": round(min_days, 1) if min_days is not None else None,
            "renewal_score": renewal_sc,
            "renewal_label": renewal_label,
            "max_utilization_pct": round(max_util, 1),
            "high_util_packages": high_util_packages,
            "utilization_score": util_sc,
            "utilization_label": util_label,
            "packages_behind": packages_behind,
            "version_score": version_sc,
            "version_label": version_label,
            "size_weight": size_w,
            "priority": priority,
            "tier": tier,
        })

    scored.sort(key=lambda c: c["priority"], reverse=True)

    # ── 5. Executive summary ───────────────────────────────────
    tier1 = [c for c in scored if c["priority"] >= 70]
    tier2 = [c for c in scored if 40 <= c["priority"] < 70]
    tier3 = [c for c in scored if c["priority"] < 40]

    critical_renewals = [c for c in scored if c["renewal_label"] in ("CRITICAL", "URGENT")]
    high_util = [c for c in scored if c["utilization_label"] in ("MAXED", "HIGH")]
    stale_versions = [c for c in scored if c["version_label"] == "STALE"]

    report.append("## Executive Summary\n")
    report.append(f"- **{len(scored)}** customers analyzed")
    report.append(f"- **{len(tier1)}** Tier 1 (Immediate Action)")
    report.append(f"- **{len(tier2)}** Tier 2 (Plan)")
    report.append(f"- **{len(tier3)}** Tier 3 (Monitor)")
    report.append(f"- **{len(critical_renewals)}** with critical/urgent renewals")
    report.append(f"- **{len(high_util)}** with high license utilization (90%+)")
    report.append(f"- **{len(stale_versions)}** with 3+ packages behind latest version")

    # ── 6. Portfolio heatmap ───────────────────────────────────
    report.append("\n## Portfolio Heatmap\n")
    report.append("| Customer | Pkgs | Licenses | Renewal | Utilization | Version | Priority |")
    report.append("|----------|------|----------|---------|-------------|---------|----------|")

    for c in scored[:50]:
        days_str = f"{int(c['min_days_to_expiry'])}d" if c["min_days_to_expiry"] is not None else "—"
        report.append(
            f"| {c['customer']} | {c['package_count']} | {c['max_licenses']} "
            f"| {c['renewal_label']} ({days_str}) "
            f"| {c['utilization_label']} ({c['max_utilization_pct']:.0f}%) "
            f"| {c['version_label']} ({c['packages_behind']}) "
            f"| **{c['priority']}** |"
        )

    if len(scored) > 50:
        report.append(f"\n_...and {len(scored) - 50} more customers in the JSON artifact._")

    # ── 7. Tier 1 detail ───────────────────────────────────────
    if tier1:
        report.append("\n## Tier 1 -- Immediate Action\n")
        for c in tier1:
            days_str = f"{int(c['min_days_to_expiry'])} days" if c["min_days_to_expiry"] is not None else "N/A"
            report.append(f"### {c['customer']} (Priority: {c['priority']})\n")
            report.append(f"- Packages: {c['package_count']}, Licenses: {c['max_licenses']} (per largest package)")
            report.append(f"- Renewal: {c['renewal_label']} ({days_str})")
            report.append(
                f"- License utilization: {c['utilization_label']} "
                f"(peak {c['max_utilization_pct']:.0f}%, "
                f"{c['high_util_packages']} package(s) at 90%+)"
            )
            report.append(f"- Version: {c['version_label']} ({c['packages_behind']} behind)")
            report.append("")

    # ── 8. Top opportunities ───────────────────────────────────
    report.append("\n## Top 10 Renewal Opportunities\n")
    renewals_sorted = sorted(
        [c for c in scored if c["min_days_to_expiry"] is not None],
        key=lambda c: c["min_days_to_expiry"],
    )[:10]
    if renewals_sorted:
        report.append("| Customer | Expires In | Packages | Licenses |")
        report.append("|----------|-----------|----------|----------|")
        for c in renewals_sorted:
            report.append(
                f"| {c['customer']} | {int(c['min_days_to_expiry'])} days "
                f"| {c['package_count']} | {c['max_licenses']} |"
            )

    report.append("\n## Top 10 License Expansion Opportunities\n")
    util_sorted = sorted(scored, key=lambda c: c["max_utilization_pct"], reverse=True)[:10]
    if util_sorted:
        report.append("| Customer | Peak Utilization | Pkgs at 90%+ | Licenses |")
        report.append("|----------|-----------------|-------------|----------|")
        for c in util_sorted:
            report.append(
                f"| {c['customer']} | {c['max_utilization_pct']:.0f}% "
                f"| {c['high_util_packages']} | {c['max_licenses']} |"
            )

    report.append("\n## Top 10 Version Upgrade Opportunities\n")
    version_sorted = sorted(scored, key=lambda c: c["packages_behind"], reverse=True)[:10]
    if version_sorted:
        report.append("| Customer | Packages Behind | Total Packages | Licenses |")
        report.append("|----------|----------------|---------------|----------|")
        for c in version_sorted:
            report.append(
                f"| {c['customer']} | {c['packages_behind']} "
                f"| {c['package_count']} | {c['max_licenses']} |"
            )

    # ── 9. Combined opportunities ──────────────────────────────
    combined = [
        c for c in scored
        if c["renewal_label"] in ("CRITICAL", "URGENT", "WATCH")
        and c["utilization_label"] in ("MAXED", "HIGH")
    ]
    if combined:
        report.append("\n## Combined Opportunities (Renewal + License Expansion)\n")
        report.append("| Customer | Renewal | Utilization | Priority |")
        report.append("|----------|---------|------------|----------|")
        for c in sorted(combined, key=lambda c: c["priority"], reverse=True):
            days_str = f"{int(c['min_days_to_expiry'])}d" if c["min_days_to_expiry"] is not None else "—"
            report.append(
                f"| {c['customer']} | {c['renewal_label']} ({days_str}) "
                f"| {c['utilization_label']} ({c['max_utilization_pct']:.0f}%) "
                f"| **{c['priority']}** |"
            )

    # ── 10. Write artifacts ────────────────────────────────────
    out_dir = state.get_working_dir()
    artifact_path = out_dir / "portfolio-insights.json"
    report_path = out_dir / "portfolio-insights.md"

    artifact = {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "lma_org": lma_alias,
        "total_licenses_queried": len(all_licenses),
        "customers_analyzed": len(scored),
        "filters": {
            "min_licenses": min_licenses,
            "min_packages": min_packages,
            "expiry_horizon_days": expiry_horizon,
        },
        "summary": {
            "tier1_count": len(tier1),
            "tier2_count": len(tier2),
            "tier3_count": len(tier3),
            "critical_renewals": len(critical_renewals),
            "high_utilization": len(high_util),
            "stale_versions": len(stale_versions),
        },
        "scoring_rules": {
            "renewal_weight": 0.35,
            "utilization_weight": 0.25,
            "version_weight": 0.20,
            "size_weight": 0.20,
        },
        "customers": scored,
    }

    try:
        artifact_path.write_text(json.dumps(artifact, indent=2, default=str))
        report_text = "\n".join(report)
        report_path.write_text(report_text)
        report.append("\n## Artifacts\n")
        report.append(f"- JSON: {artifact_path}")
        report.append(f"- Report: {report_path}")
    except OSError as e:
        logger.warning("Could not write artifacts: %s", e)
        report.append(f"\n_Could not write files: {e}_")

    # ── 11. Scoring methodology ────────────────────────────────
    report.append("\n---\n")
    report.append("## Scoring Methodology\n")
    report.append(
        "All scores are computed by the tool using fixed rules "
        "(not AI-generated). Priority = "
        "(Renewal x 0.35) + (Utilization x 0.25) + (Version x 0.20) + (Size x 0.20)\n"
    )
    report.append("- **Renewal:** CRITICAL (<30d)=100, URGENT (<90d)=80, WATCH (<180d)=50, PLAN (<365d)=20")
    report.append("- **Utilization:** MAXED (100%)=100, HIGH (90%+)=80, MODERATE (75%+)=50")
    report.append("- **Version:** STALE (3+ behind)=100, BEHIND (1-2)=60, CURRENT=0")
    report.append("- **Size:** Large (>500 licenses or >10 pkgs) x1.5, Medium x1.0, Small x0.7")
    report.append("")
    report.append(
        "_Licenses column shows the largest per-package license count "
        "(not summed across packages). Utilization is the highest "
        "used/licensed ratio across all packages for that customer._"
    )

    report.append("\n## STATUS: READY\n")

    return "\n".join(report)
