"""complyform-founder-metrics — daily KPI aggregation from all data sources.

Trigger: Daily Cloud Scheduler (08:00 UTC).

Aggregates Paddle, PyPI, Homebrew, GCP Billing, Postmark, Xero, and Firestore
data into a single ``founder_metrics/{YYYY-MM-DD}`` Firestore document for the
founder dashboard.

Contract: every external API failure writes ``None`` for that field — the
function never aborts.  Always returns 200 to Cloud Scheduler.
"""

from __future__ import annotations

import logging
import math
from datetime import UTC, datetime, timedelta
from typing import Any

import httpx

from backend.ops._shared import cloud_function_wrapper, log_action
from backend.shared.firestore import get_db
from backend.shared.tier_config import TIER_CONFIG

_log = logging.getLogger(__name__)

_PADDLE_API_BASE = "https://api.paddle.com"

# Annual prices from TIER_CONFIG for MRR calculation.
_TIER_ANNUAL_PRICES: dict[str, int] = {
    tier: int(cfg["price_annual"])  # type: ignore[arg-type]
    for tier, cfg in TIER_CONFIG.items()
    if isinstance(cfg.get("price_annual"), int) and int(cfg["price_annual"]) > 0  # type: ignore[arg-type]
}


async def _get_paddle_api_key() -> str:
    """Retrieve Paddle API key from Secret Manager."""
    from google.cloud import secretmanager

    client = secretmanager.SecretManagerServiceClient()
    name = "projects/complyform-prod/secrets/paddle-api-key/versions/latest"
    response = client.access_secret_version(request={"name": name})
    return response.payload.data.decode("utf-8")


async def _get_github_token() -> str:
    """Retrieve GitHub token from Secret Manager."""
    from google.cloud import secretmanager

    client = secretmanager.SecretManagerServiceClient()
    name = "projects/complyform-prod/secrets/github-token/versions/latest"
    response = client.access_secret_version(request={"name": name})
    return response.payload.data.decode("utf-8")


# ---------------------------------------------------------------------------
# Data source fetchers — each returns partial metrics or empty dict on error
# ---------------------------------------------------------------------------


async def _fetch_paddle_metrics(
    client: httpx.AsyncClient,
    paddle_key: str,
) -> dict[str, Any]:
    """Fetch subscription/revenue data from Paddle API."""
    headers = {
        "Authorization": f"Bearer {paddle_key}",
        "Content-Type": "application/json",
    }
    try:
        # Active subscriptions
        subs: list[dict[str, Any]] = []
        url = f"{_PADDLE_API_BASE}/subscriptions?status=active&per_page=200"
        while url:
            resp = await client.get(url, headers=headers)
            resp.raise_for_status()
            body = resp.json()
            subs.extend(body.get("data", []))
            url = body.get("meta", {}).get("pagination", {}).get("next", "")

        # Count by tier and compute MRR
        tier_counts: dict[str, int] = {}
        mrr = 0.0
        for sub in subs:
            # Resolve tier from custom_data or items
            tier = _resolve_subscription_tier(sub)
            tier_counts[tier] = tier_counts.get(tier, 0) + 1
            annual = _TIER_ANNUAL_PRICES.get(tier, 0)
            mrr += annual / 12

        arr = mrr * 12

        # Revenue by tier
        revenue_by_tier: dict[str, float] = {}
        for tier, count in tier_counts.items():
            annual = _TIER_ANNUAL_PRICES.get(tier, 0)
            revenue_by_tier[tier] = count * annual / 12

        # Customer count
        total_customers = len(subs)

        # Cancelled in last 30 days for logo churn
        cancelled_count = await _fetch_cancelled_count(client, headers)
        start_customers = total_customers + cancelled_count
        logo_churn = (
            (cancelled_count / start_customers * 100) if start_customers > 0 else 0.0
        )

        return {
            "mrr": round(mrr, 2),
            "arr": round(arr, 2),
            "total_customers": total_customers,
            "revenue_by_tier": revenue_by_tier,
            "logo_churn_30d": round(logo_churn, 2),
        }
    except Exception:
        _log.exception("Paddle metrics fetch failed")
        return {
            "mrr": None,
            "arr": None,
            "total_customers": None,
            "revenue_by_tier": None,
            "logo_churn_30d": None,
        }


def _resolve_subscription_tier(sub: dict[str, Any]) -> str:
    """Resolve tier name from subscription data."""
    custom_data = sub.get("custom_data") or {}
    tier = custom_data.get("tier")
    if tier and isinstance(tier, str):
        return tier
    # Fallback: check items for price_id mapping
    items = sub.get("items", [])
    for item in items:
        price = item.get("price", {})
        price_id = price.get("id", "")
        if "pro" in price_id:
            return "pro"
        if "team" in price_id:
            return "team"
        if "agency" in price_id:
            return "agency"
        if "enterprise_unlimited" in price_id:
            return "enterprise_unlimited"
        if "enterprise" in price_id:
            return "enterprise"
    return "unknown"


async def _fetch_cancelled_count(
    client: httpx.AsyncClient,
    headers: dict[str, str],
) -> int:
    """Count subscriptions cancelled in the last 30 days."""
    try:
        url = f"{_PADDLE_API_BASE}/subscriptions?status=canceled&per_page=200"
        count = 0
        cutoff = (datetime.now(UTC) - timedelta(days=30)).isoformat()
        while url:
            resp = await client.get(url, headers=headers)
            resp.raise_for_status()
            body = resp.json()
            for sub in body.get("data", []):
                canceled_at = sub.get("canceled_at", "")
                if canceled_at and canceled_at >= cutoff:
                    count += 1
            url = body.get("meta", {}).get("pagination", {}).get("next", "")
        return count
    except Exception:
        _log.exception("Failed to fetch cancelled subscriptions")
        return 0


async def _fetch_pypi_metrics(client: httpx.AsyncClient) -> dict[str, Any]:
    """Fetch PyPI download stats (public, no auth)."""
    try:
        resp = await client.get(
            "https://pypistats.org/api/packages/complyform/overall",
            headers={"Accept": "application/json"},
        )
        resp.raise_for_status()
        data = resp.json()
        total = sum(row.get("downloads", 0) for row in data.get("data", []))
        return {"pypi_installs_total": total}
    except Exception:
        _log.exception("PyPI metrics fetch failed")
        return {"pypi_installs_total": None}


async def _fetch_homebrew_metrics(client: httpx.AsyncClient) -> dict[str, Any]:
    """Fetch Homebrew install count (public, no auth)."""
    try:
        resp = await client.get(
            "https://formulae.brew.sh/api/analytics/install/365d.json"
        )
        resp.raise_for_status()
        data = resp.json()
        installs = 0
        for item in data.get("items", []):
            if item.get("formula", "").lower() == "complyform":
                installs = item.get("count", 0)
                break
        return {"homebrew_installs_365d": installs}
    except Exception:
        _log.exception("Homebrew metrics fetch failed")
        return {"homebrew_installs_365d": None}


async def _fetch_gcp_billing(client: httpx.AsyncClient) -> dict[str, Any]:
    """Fetch current month GCP spend via Cloud Billing API (ADC)."""
    try:
        import google.auth
        import google.auth.transport.requests

        credentials, _project = google.auth.default(
            scopes=["https://www.googleapis.com/auth/cloud-billing.readonly"]
        )
        auth_req = google.auth.transport.requests.Request()
        credentials.refresh(auth_req)
        token = credentials.token

        resp = await client.get(
            "https://cloudbilling.googleapis.com/v1/billingAccounts",
            headers={"Authorization": f"Bearer {token}"},
        )
        resp.raise_for_status()
        # Simplified: sum current month charges from first billing account
        accounts = resp.json().get("billingAccounts", [])
        if not accounts:
            return {"gcp_monthly_spend": 0.0}

        # Note: actual billing export would use BigQuery; this is a placeholder
        return {"gcp_monthly_spend": None}
    except Exception:
        _log.exception("GCP Billing fetch failed")
        return {"gcp_monthly_spend": None}


async def _fetch_postmark_metrics(client: httpx.AsyncClient) -> dict[str, Any]:
    """Fetch Postmark delivery stats."""
    try:
        from backend.billing.email_sender import _get_postmark_token

        token = await _get_postmark_token()
        headers = {
            "Accept": "application/json",
            "X-Postmark-Server-Token": token,
        }
        resp = await client.get(
            "https://api.postmarkapp.com/stats/outbound",
            headers=headers,
        )
        resp.raise_for_status()
        data = resp.json()
        sent = data.get("Sent", 0)
        bounced = data.get("Bounced", 0)
        spam = data.get("SpamComplaints", 0)
        total = sent + bounced + spam
        return {
            "postmark_delivery_rate": round(sent / total * 100, 2)
            if total > 0
            else None,
            "postmark_bounce_rate": round(bounced / total * 100, 2)
            if total > 0
            else None,
            "postmark_spam_rate": round(spam / total * 100, 2) if total > 0 else None,
        }
    except Exception:
        _log.exception("Postmark metrics fetch failed")
        return {
            "postmark_delivery_rate": None,
            "postmark_bounce_rate": None,
            "postmark_spam_rate": None,
        }


async def _fetch_xero_health() -> dict[str, Any]:
    """Check Xero invoice sync freshness (if OAuth token available)."""
    try:
        db = get_db()
        doc = await db.collection("integrations").document("xero").get()
        if not doc.exists:
            return {"xero_sync_health": "not_configured"}
        data = doc.to_dict() or {}
        last_sync = data.get("last_invoice_sync")
        if not last_sync:
            return {"xero_sync_health": "no_sync_recorded"}
        last_dt = datetime.fromisoformat(last_sync)
        days_behind = (datetime.now(UTC) - last_dt).days
        health = "ok" if days_behind <= 7 else f"behind_{days_behind}d"
        return {"xero_sync_health": health}
    except Exception:
        _log.exception("Xero health check failed")
        return {"xero_sync_health": None}


async def _fetch_firestore_metrics() -> dict[str, Any]:
    """Aggregate Firestore collections for NPS, at-risk, scan frequency."""
    metrics: dict[str, Any] = {
        "nps_score": None,
        "activation_at_risk_count": None,
        "scan_frequency_7d": None,
        "scan_frequency_30d": None,
    }
    db = get_db()

    # NPS from customer_feedback
    try:
        promoters = 0
        detractors = 0
        passives = 0
        async for doc in db.collection("customer_feedback").stream():
            data = doc.to_dict() or {}
            score = data.get("score")
            if score is None:
                continue
            score = int(score)
            if score >= 9:
                promoters += 1
            elif score >= 7:
                passives += 1
            else:
                detractors += 1
        total = promoters + passives + detractors
        if total > 0:
            metrics["nps_score"] = round(
                (promoters / total - detractors / total) * 100, 1
            )
    except Exception:
        _log.exception("NPS calculation failed")

    # At-risk customers
    try:
        query = db.collection("customer_lifecycle").where("at_risk", "==", True)
        count = 0
        async for _ in query.stream():
            count += 1
        metrics["activation_at_risk_count"] = count
    except Exception:
        _log.exception("At-risk count failed")

    # Scan frequency
    try:
        now = datetime.now(UTC)
        cutoff_7d = (now - timedelta(days=7)).isoformat()
        cutoff_30d = (now - timedelta(days=30)).isoformat()
        count_7d = 0
        count_30d = 0
        async for doc in db.collection("scan_activity").stream():
            data = doc.to_dict() or {}
            last_scan = data.get("last_scan", "")
            if last_scan >= cutoff_7d:
                count_7d += 1
            if last_scan >= cutoff_30d:
                count_30d += 1
        metrics["scan_frequency_7d"] = count_7d
        metrics["scan_frequency_30d"] = count_30d
    except Exception:
        _log.exception("Scan frequency calculation failed")

    return metrics


def compute_nps(promoters: int, passives: int, detractors: int) -> float | None:
    """Compute NPS from raw counts.  Scale: -100 to 100."""
    total = promoters + passives + detractors
    if total == 0:
        return None
    return round((promoters / total - detractors / total) * 100, 1)


def compute_burn_multiple(net_burn: float, net_new_arr: float) -> float:
    """Burn multiple = net_burn / net_new_ARR.  Infinity if no growth."""
    if net_new_arr == 0:
        return math.inf
    return round(net_burn / net_new_arr, 2)


@cloud_function_wrapper("complyform-founder-metrics")
async def founder_metrics(request: Any) -> dict[str, Any]:
    """Aggregate all KPIs to Firestore for founder dashboard."""
    today = datetime.now(UTC).strftime("%Y-%m-%d")
    result: dict[str, Any] = {"computed_at": datetime.now(UTC).isoformat()}

    async with httpx.AsyncClient(timeout=30.0) as client:
        # Paddle
        try:
            paddle_key = await _get_paddle_api_key()
            paddle = await _fetch_paddle_metrics(client, paddle_key)
        except Exception:
            _log.exception("Paddle key retrieval failed")
            paddle = {
                "mrr": None,
                "arr": None,
                "total_customers": None,
                "revenue_by_tier": None,
                "logo_churn_30d": None,
            }
        result.update(paddle)

        # Derived metrics
        mrr = result.get("mrr")
        gcp_spend = result.get("gcp_monthly_spend")
        result["mrr_growth_rate"] = None  # Requires historical comparison
        result["nrr"] = None  # Requires expansion/contraction tracking
        result["cac_payback_days"] = None  # Requires CAC data

        # Burn multiple (placeholder — requires actual burn data)
        result["burn_multiple"] = None

        # Cost-to-revenue
        if mrr and gcp_spend is not None:
            result["cost_to_revenue_ratio"] = round(gcp_spend / mrr * 100, 2)
        else:
            result["cost_to_revenue_ratio"] = None

        # PyPI
        pypi = await _fetch_pypi_metrics(client)
        result.update(pypi)

        # Homebrew
        homebrew = await _fetch_homebrew_metrics(client)
        result.update(homebrew)

        # GCP Billing
        gcp = await _fetch_gcp_billing(client)
        result.update(gcp)

        # Postmark
        postmark = await _fetch_postmark_metrics(client)
        result.update(postmark)

    # Xero
    xero = await _fetch_xero_health()
    result.update(xero)

    # Firestore aggregations
    firestore = await _fetch_firestore_metrics()
    result.update(firestore)

    # Placeholder fields
    result.setdefault("open_issues_p1_p2", None)
    result.setdefault("corpus_triples", None)

    # Write to Firestore
    db = get_db()
    await db.collection("founder_metrics").document(today).set(result)

    log_action(
        customer_id=None,
        action="founder_metrics_written",
        record_count=len(result),
        date=today,
    )
    return {"date": today, "fields_written": len(result)}


entry_point = founder_metrics
