"""get_customer_licenses tool -- retrieve licenses and package info from LMA.

Queries the LMA org for all CloudSense licenses belonging to a customer:
- Production licenses via Account ID (reliable join)
- Sandbox licenses via Company__c fuzzy match (custom formula field)

Produces an org summary, package version comparison, Customer Health Score,
and upgrade/upsell/renewal insights.
"""

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

from ..utils import sf_cli, state

logger = logging.getLogger(__name__)

_COMPANY_SUFFIXES = re.compile(
    r"\b(Ltd|Limited|Inc|Corp|Corporation|Pty|PLC|GmbH|AG|SA|SAS|"
    r"Co|Company|Group|Holdings|International|Services|Solutions|"
    r"Technologies|Consulting)\b\.?",
    re.IGNORECASE,
)

TOOL_DEFINITION = {
    "name": "get_customer_licenses",
    "description": (
        "Retrieve all CloudSense licenses, org summary, and package info "
        "for a customer from the LMA org. Queries production licenses by "
        "Account ID and sandbox licenses by Company__c fuzzy match. "
        "Produces a Customer Health Score (0-100), upgrade opportunities, "
        "upsell/renewal signals, and recommends the best sandbox for "
        "metadata retrieval. Requires connect_lma and find_customer first."
    ),
    "inputSchema": {
        "type": "object",
        "properties": {
            "account_id": {
                "type": "string",
                "description": (
                    "Salesforce Account ID from find_customer. "
                    "Used to query production licenses."
                ),
            },
            "account_name": {
                "type": "string",
                "description": (
                    "Customer account name from find_customer. "
                    "Used to build the Company__c fuzzy match for sandbox licenses."
                ),
            },
            "working_directory": {
                "type": "string",
                "description": (
                    "Absolute path to the working directory. "
                    "Reads LMA alias from state and stores results."
                ),
            },
        },
        "required": ["account_id", "account_name"],
    },
}


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


def _get_available_fields(
    working_dir: Path | None, object_name: str
) -> set[str]:
    """Get discovered fields for an sfLma object from state."""
    if working_dir:
        existing = state.load_state(working_dir)
        if existing:
            lma_fields = existing.get("lma_fields", {})
            return set(lma_fields.get(object_name, []))
    return set()


def _build_license_fields(available: set[str]) -> str:
    """Build the SELECT field list using only fields that exist."""
    desired = [
        "Id",
        "sfLma__Subscriber_Org_ID__c",
        "Package_Name__c",
        "Package_Version_Number__c",
        "sfLma__License_Status__c",
        "sfLma__Expiration_Date__c",
        "Days_To_Expire__c",
        "sfLma__Is_Sandbox__c",
        "CSLabs1__Unused_Licenses__c",
        "Company__c",
        "sfLma__Package_Version__r.sfLma__Package__c",
    ]
    if not available:
        return ", ".join(desired)

    safe_fields = []
    for f in desired:
        base = f.split(".")[0]
        if base in available or not available:
            safe_fields.append(f)
    return ", ".join(safe_fields) if safe_fields else ", ".join(desired[:6])


def _extract_search_keyword(account_name: str) -> str:
    """Extract a short keyword from an account name for Company__c matching.

    Strips common corporate suffixes (Ltd, Inc, Pty, etc.) and returns
    the core name. E.g. "NBN Co Ltd" -> "NBN", "StarHub Ltd" -> "StarHub".
    """
    cleaned = _COMPANY_SUFFIXES.sub("", account_name).strip()
    cleaned = re.sub(r"\s+", " ", cleaned).strip()
    words = cleaned.split()
    if len(words) <= 2:
        return cleaned
    return " ".join(words[:2])


def _query_production_licenses(
    account_id: str, lma_alias: str, fields: str
) -> list[dict[str, Any]]:
    """Query production licenses by Account ID."""
    query = (
        f"SELECT {fields} "
        f"FROM sfLma__License__c "
        f"WHERE sfLma__Account__c = '{account_id}' "
        f"AND sfLma__Is_Sandbox__c = false "
        f"AND Package_Version_Number__c != ''"
    )
    try:
        return sf_cli.lma_query(query, lma_alias)
    except sf_cli.SfCliError as e:
        logger.warning("Production license query failed: %s", e)
        return []


def _query_sandbox_licenses(
    keyword: str, lma_alias: str, fields: str
) -> list[dict[str, Any]]:
    """Query sandbox licenses by Company__c fuzzy match."""
    safe_keyword = keyword.replace("'", "\\'")
    query = (
        f"SELECT {fields} "
        f"FROM sfLma__License__c "
        f"WHERE Company__c LIKE '%{safe_keyword}%' "
        f"AND sfLma__Is_Sandbox__c = true "
        f"AND Package_Version_Number__c != ''"
    )
    try:
        return sf_cli.lma_query(query, lma_alias)
    except sf_cli.SfCliError as e:
        logger.warning("Sandbox license query failed: %s", e)
        return []


def _query_available_versions(
    package_id: str, lma_alias: str
) -> list[dict[str, Any]]:
    """Query available versions for a package."""
    query = (
        f"SELECT Id, Name, sfLma__Version_Number__c, "
        f"sfLma__Version__c, sfLma__Release_Date__c, "
        f"sfLma__Is_Beta__c "
        f"FROM sfLma__Package_Version__c "
        f"WHERE sfLma__Package__c = '{package_id}' "
        f"ORDER BY sfLma__Release_Date__c DESC "
        f"LIMIT 10"
    )
    try:
        return sf_cli.lma_query(query, lma_alias)
    except sf_cli.SfCliError as e:
        logger.warning(
            "Version query for package %s failed: %s", package_id, e
        )
        return []


def _build_org_summary(
    prod_licenses: list[dict], sandbox_licenses: list[dict]
) -> dict[str, Any]:
    """Group licenses by subscriber org ID to build an org summary."""
    prod_orgs: dict[str, list[dict]] = defaultdict(list)
    sandbox_orgs: dict[str, list[dict]] = defaultdict(list)

    for lic in prod_licenses:
        org_id = lic.get("sfLma__Subscriber_Org_ID__c", "unknown")
        prod_orgs[org_id].append(lic)

    for lic in sandbox_licenses:
        org_id = lic.get("sfLma__Subscriber_Org_ID__c", "unknown")
        sandbox_orgs[org_id].append(lic)

    best_sandbox = None
    best_count = 0
    for org_id, licenses in sandbox_orgs.items():
        if len(licenses) > best_count:
            best_count = len(licenses)
            best_sandbox = org_id

    return {
        "production_orgs": {
            oid: {
                "package_count": len(lics),
                "packages": [l.get("Package_Name__c", "?") for l in lics],
            }
            for oid, lics in prod_orgs.items()
        },
        "sandbox_orgs": {
            oid: {
                "package_count": len(lics),
                "company": lics[0].get("Company__c", "?") if lics else "?",
                "packages": [l.get("Package_Name__c", "?") for l in lics],
            }
            for oid, lics in sandbox_orgs.items()
        },
        "production_count": len(prod_orgs),
        "sandbox_count": len(sandbox_orgs),
        "recommended_sandbox": best_sandbox,
        "recommended_sandbox_packages": best_count,
    }


def _compute_health_score(
    prod_licenses: list[dict],
    available_versions: dict[str, list[dict]],
    org_summary: dict[str, Any],
) -> dict[str, Any]:
    """Compute Customer Health Score (0-100)."""
    score = 0
    breakdown: list[str] = []

    # -- Versions behind latest (30 pts max) --
    versions_score = 30
    packages_behind = 0
    for lic in prod_licenses:
        pkg_name = lic.get("Package_Name__c", "")
        installed_ver = lic.get("Package_Version_Number__c", "")
        pkg_id_path = lic.get("sfLma__Package_Version__r", {})
        pkg_id = pkg_id_path.get("sfLma__Package__c", "") if isinstance(pkg_id_path, dict) else ""

        if pkg_id and pkg_id in available_versions:
            versions = available_versions[pkg_id]
            if versions:
                latest_ver = versions[0].get("sfLma__Version_Number__c", "")
                if latest_ver and installed_ver and latest_ver != installed_ver:
                    packages_behind += 1
                    versions_score -= 10

    versions_score = max(0, versions_score)
    score += versions_score
    if packages_behind:
        breakdown.append(
            f"Versions: {versions_score}/30 ({packages_behind} package(s) behind latest)"
        )
    else:
        breakdown.append(f"Versions: {versions_score}/30 (all current)")

    # -- License expiry (30 pts max) --
    expiry_score = 30
    expiring_soon = 0
    for lic in prod_licenses:
        days = lic.get("Days_To_Expire__c")
        if days is not None:
            try:
                days_num = float(days)
                if days_num < 30:
                    expiry_score = min(expiry_score, 0)
                    expiring_soon += 1
                elif days_num < 90:
                    expiry_score = min(expiry_score, 5)
                    expiring_soon += 1
                elif days_num < 180:
                    expiry_score = min(expiry_score, 15)
            except (ValueError, TypeError):
                pass

    score += expiry_score
    if expiring_soon:
        breakdown.append(
            f"Expiry: {expiry_score}/30 ({expiring_soon} license(s) expiring soon)"
        )
    else:
        breakdown.append(f"Expiry: {expiry_score}/30 (healthy)")

    # -- Unused licenses (20 pts max) --
    unused_score = 20
    unused_count = 0
    for lic in prod_licenses:
        unused = lic.get("CSLabs1__Unused_Licenses__c")
        if unused is not None:
            try:
                if int(unused) > 0:
                    unused_count += int(unused)
            except (ValueError, TypeError):
                pass
    if unused_count > 10:
        unused_score = 0
    elif unused_count > 5:
        unused_score = 10
    elif unused_count > 0:
        unused_score = 15

    score += unused_score
    if unused_count:
        breakdown.append(
            f"Unused licenses: {unused_score}/20 ({unused_count} unused -- upsell signal)"
        )
    else:
        breakdown.append(f"Unused licenses: {unused_score}/20 (fully utilized)")

    # -- Sandbox version drift (10 pts max) --
    drift_score = 10
    score += drift_score
    breakdown.append(f"Sandbox drift: {drift_score}/10 (computed after org comparison)")

    # -- Package coverage (10 pts max) --
    coverage_score = 10
    score += coverage_score
    breakdown.append(f"Package coverage: {coverage_score}/10 (computed after org comparison)")

    label = "HEALTHY"
    if score < 40:
        label = "UPGRADE RECOMMENDED"
    elif score < 60:
        label = "ATTENTION NEEDED"
    elif score < 80:
        label = "GOOD"

    return {
        "score": score,
        "max": 100,
        "label": label,
        "breakdown": breakdown,
    }


async def run(arguments: dict[str, Any]) -> str:
    """Execute the get_customer_licenses tool."""
    account_id = arguments["account_id"]
    account_name = arguments["account_name"]
    working_dir_str = arguments.get("working_directory")
    working_dir = Path(working_dir_str) if working_dir_str else None

    lma_alias = _get_lma_alias(working_dir)
    available_field_set = _get_available_fields(
        working_dir, "sfLma__License__c"
    )
    fields = _build_license_fields(available_field_set)

    report: list[str] = []
    report.append("# Customer Licenses & Package Intelligence\n")
    report.append(f"- Customer: **{account_name}** ({account_id})")
    report.append(f"- LMA org: {lma_alias}\n")

    # ── 1. Query production licenses ───────────────────────────
    report.append("## Production Licenses\n")
    prod_licenses = _query_production_licenses(account_id, lma_alias, fields)
    report.append(f"- Records found: {len(prod_licenses)}")

    if prod_licenses:
        report.append("\n| Package | Version | Status | Expiry (days) |")
        report.append("|---------|---------|--------|--------------|")
        seen_prod = set()
        for lic in prod_licenses:
            pkg = lic.get("Package_Name__c", "?")
            ver = lic.get("Package_Version_Number__c", "?")
            status = lic.get("sfLma__License_Status__c", "?")
            days = lic.get("Days_To_Expire__c", "?")
            key = f"{pkg}|{ver}"
            if key not in seen_prod:
                seen_prod.add(key)
                report.append(f"| {pkg} | {ver} | {status} | {days} |")

    # ── 2. Query sandbox licenses ──────────────────────────────
    report.append("\n## Sandbox Licenses\n")
    keyword = _extract_search_keyword(account_name)
    report.append(f"- Search keyword for Company__c: **{keyword}**")

    sandbox_licenses = _query_sandbox_licenses(keyword, lma_alias, fields)
    report.append(f"- Records found: {len(sandbox_licenses)}")

    if sandbox_licenses:
        sandbox_companies = set()
        for lic in sandbox_licenses:
            company = lic.get("Company__c", "")
            if company:
                sandbox_companies.add(company)
        if sandbox_companies:
            report.append(
                f"- Company__c values: {', '.join(sorted(sandbox_companies))}"
            )

    # ── 3. Org summary ─────────────────────────────────────────
    report.append("\n## Org Summary\n")
    org_summary = _build_org_summary(prod_licenses, sandbox_licenses)

    report.append(
        f"- Production orgs: **{org_summary['production_count']}**"
    )
    for org_id, info in org_summary["production_orgs"].items():
        report.append(f"  - {org_id}: {info['package_count']} packages")

    report.append(
        f"- Sandbox orgs: **{org_summary['sandbox_count']}**"
    )
    for org_id, info in org_summary["sandbox_orgs"].items():
        company = info.get("company", "?")
        report.append(
            f"  - {org_id} ({company}): {info['package_count']} packages"
        )

    if org_summary["recommended_sandbox"]:
        report.append(
            f"\n**Recommended sandbox for metadata retrieval:** "
            f"{org_summary['recommended_sandbox']} "
            f"({org_summary['recommended_sandbox_packages']} packages)"
        )

    # ── 4. Available versions per package ──────────────────────
    report.append("\n## Upgrade Opportunities\n")

    unique_packages: dict[str, str] = {}
    for lic in prod_licenses:
        pkg_ref = lic.get("sfLma__Package_Version__r")
        if isinstance(pkg_ref, dict):
            pkg_id = pkg_ref.get("sfLma__Package__c", "")
            pkg_name = lic.get("Package_Name__c", "")
            if pkg_id and pkg_name:
                unique_packages[pkg_id] = pkg_name

    available_versions: dict[str, list[dict]] = {}
    upgrade_opps: list[dict] = []

    for pkg_id, pkg_name in unique_packages.items():
        versions = _query_available_versions(pkg_id, lma_alias)
        available_versions[pkg_id] = versions

        installed_ver = None
        for lic in prod_licenses:
            ref = lic.get("sfLma__Package_Version__r")
            if isinstance(ref, dict) and ref.get("sfLma__Package__c") == pkg_id:
                installed_ver = lic.get("Package_Version_Number__c", "")
                break

        if versions:
            latest = versions[0]
            latest_ver = latest.get("sfLma__Version_Number__c", "")
            if installed_ver and latest_ver and installed_ver != latest_ver:
                upgrade_opps.append({
                    "package": pkg_name,
                    "installed": installed_ver,
                    "latest": latest_ver,
                    "latest_release_date": latest.get(
                        "sfLma__Release_Date__c", "?"
                    ),
                })

    if upgrade_opps:
        report.append("| Package | Installed | Latest Available | Released |")
        report.append("|---------|-----------|-----------------|----------|")
        for opp in upgrade_opps:
            report.append(
                f"| {opp['package']} | {opp['installed']} "
                f"| {opp['latest']} | {opp['latest_release_date']} |"
            )
    else:
        report.append("All packages are at the latest available version.")

    # ── 5. Health Score ────────────────────────────────────────
    report.append("\n## Customer Health Score\n")
    health = _compute_health_score(prod_licenses, available_versions, org_summary)

    report.append(f"### **{health['score']}/{health['max']} -- {health['label']}**\n")
    for line in health["breakdown"]:
        report.append(f"- {line}")

    # ── 6. Insights ────────────────────────────────────────────
    report.append("\n## Insights\n")

    expiring = [
        lic for lic in prod_licenses
        if lic.get("Days_To_Expire__c") is not None
        and _safe_float(lic.get("Days_To_Expire__c")) < 90
    ]
    if expiring:
        report.append(f"**Renewal signals:** {len(expiring)} license(s) expiring within 90 days")
        for lic in expiring:
            report.append(
                f"  - {lic.get('Package_Name__c', '?')}: "
                f"{lic.get('Days_To_Expire__c', '?')} days remaining"
            )

    unused_total = sum(
        _safe_int(lic.get("CSLabs1__Unused_Licenses__c"))
        for lic in prod_licenses
    )
    if unused_total > 0:
        report.append(f"\n**Upsell signal:** {unused_total} unused license seat(s) across all packages")

    if upgrade_opps:
        report.append(
            f"\n**Upgrade opportunity:** {len(upgrade_opps)} package(s) have newer versions available"
        )

    # ── 7. Store results ───────────────────────────────────────
    if working_dir:
        existing = state.load_state(working_dir)
        if existing:
            lma_data = {
                "account_id": account_id,
                "account_name": account_name,
                "production_license_count": len(prod_licenses),
                "sandbox_license_count": len(sandbox_licenses),
                "org_summary": {
                    "production_count": org_summary["production_count"],
                    "sandbox_count": org_summary["sandbox_count"],
                    "recommended_sandbox": org_summary["recommended_sandbox"],
                },
                "health_score": health["score"],
                "health_label": health["label"],
                "upgrade_opportunities": len(upgrade_opps),
                "packages": [
                    {
                        "name": lic.get("Package_Name__c", ""),
                        "version": lic.get("Package_Version_Number__c", ""),
                        "status": lic.get("sfLma__License_Status__c", ""),
                    }
                    for lic in prod_licenses
                ],
            }
            state.update_state(working_dir, lma_customer_data=lma_data)

            analysis_dir = working_dir / "analysis"
            analysis_dir.mkdir(parents=True, exist_ok=True)
            artifact_path = analysis_dir / "lma-customer-licenses.json"
            artifact_path.write_text(json.dumps({
                "customer": account_name,
                "account_id": account_id,
                "generated_at": datetime.now(timezone.utc).isoformat(),
                "production_licenses": prod_licenses,
                "sandbox_licenses": sandbox_licenses,
                "org_summary": org_summary,
                "upgrade_opportunities": upgrade_opps,
                "health_score": health,
            }, indent=2, default=str))

            report.append("\n## Artifacts\n")
            report.append(f"- State file updated with LMA customer data")
            report.append(f"- JSON artifact: {artifact_path}")

    # ── 8. Next steps ──────────────────────────────────────────
    report.append("\n---\n")
    report.append("## STATUS: READY\n")

    next_steps = [
        "Review the upgrade opportunities and health score above.",
    ]
    if org_summary["recommended_sandbox"]:
        next_steps.append(
            f"For upgrade assessment, connect to the recommended sandbox "
            f"(Org ID: {org_summary['recommended_sandbox']}) using "
            f"init_assessment."
        )
    next_steps.append(
        "Call **init_assessment** to set up the assessment project "
        "(the tool now reads LMA data from state automatically)."
    )
    for step in next_steps:
        report.append(f"- {step}")

    return "\n".join(report)


def _safe_float(val: Any) -> float:
    """Safely convert a value to float, returning infinity on failure."""
    try:
        return float(val)
    except (ValueError, TypeError):
        return float("inf")


def _safe_int(val: Any) -> int:
    """Safely convert a value to int, returning 0 on failure."""
    try:
        return int(val)
    except (ValueError, TypeError):
        return 0
