"""Account-level billing and usage endpoints for the customer dashboard."""

from __future__ import annotations

import logging
import os
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple

import httpx
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select, text
from sqlalchemy.ext.asyncio import AsyncSession

from ...db import get_session
from .auth_helpers import resolve_account_id
from ...auth_core import get_current_identity
from ... import settings as saas_settings
from ...config.tier_limits import (
    TIER_CONFIG as ENRICHMENT_CONFIG,
    get_tier_config,
    get_tier_limits,
)
from ...config.product_capabilities import CAPABILITIES_CONFIG
from ...model_defs.salesforce_ai_reports import SalesforceAIReport
from ...models import (
    Account,
    AccountEntitlements,
    AccountMember,
    EnrichmentCreditLedger,
    EnrichmentCreditBucket,
    SeatTransaction,
    SubscriptionEvent,
    UsageLedger,
    UsageQuotaModel,
)
from ...services.usage_gateway import UsageGateway, BYO_ADDON_KEY

log = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v2", tags=["account-billing"])


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


async def _resolve_account_id(identity: Dict[str, Any], db: AsyncSession) -> str:
    account_id, _clerk_org_id = await resolve_account_id(identity, db)
    return account_id


async def _fetch_usage_quota(
    db: AsyncSession, account_id: str
) -> Optional[UsageQuotaModel]:
    result = await db.execute(
        select(UsageQuotaModel).where(UsageQuotaModel.account_id == account_id)
    )
    return result.scalar_one_or_none()


async def _fetch_entitlements(
    db: AsyncSession, account_id: str
) -> Optional[AccountEntitlements]:
    result = await db.execute(
        select(AccountEntitlements).where(AccountEntitlements.account_id == account_id)
    )
    return result.scalar_one_or_none()


async def _fetch_account(db: AsyncSession, account_id: str) -> Optional[Account]:
    result = await db.execute(select(Account).where(Account.id == account_id))
    return result.scalar_one_or_none()


async def _fetch_account_members(
    db: AsyncSession, account_id: str
) -> List[AccountMember]:
    result = await db.execute(
        select(AccountMember).where(AccountMember.account_id == account_id)
    )
    return result.scalars().all()


async def _fetch_billing_interval(db: AsyncSession, account_id: str) -> Optional[str]:
    # Primary: look up via accounts.stripe_subscription_id -> subscription -> plan
    query = text(
        """
        SELECT p.billing_interval
        FROM subscription s
        JOIN plan p ON s.plan_id = p.id
        JOIN accounts a ON a.stripe_subscription_id = s.stripe_subscription_id
        WHERE a.id = :account_id
        AND s.status IN ('active', 'trialing')
        LIMIT 1
        """
    )
    result = await db.execute(query, {"account_id": account_id})
    row = result.fetchone()
    if row and row[0]:
        return row[0]

    # Fallback: look up via subscription.account_id directly (skips accounts table join)
    # Cast UUID to text since account_id is a Clerk org_id string, not a UUID
    fallback_query = text(
        """
        SELECT p.billing_interval
        FROM subscription s
        JOIN plan p ON s.plan_id = p.id
        WHERE s.account_id::text = :account_id
        AND s.status IN ('active', 'trialing')
        ORDER BY s.updated_at DESC
        LIMIT 1
        """
    )
    result = await db.execute(fallback_query, {"account_id": account_id})
    row = result.fetchone()
    return row[0] if row else None


def _coalesce_zero(value: Optional[int]) -> int:
    return int(value or 0)


def _normalize_tier_value(value: Optional[str]) -> Optional[str]:
    if value is None:
        return None
    normalized = str(value).strip().lower()
    return normalized or None


def _tier_rank(value: Optional[str]) -> int:
    normalized = _normalize_tier_value(value)
    if normalized == "scale":
        return 2
    if normalized == "pro":
        return 1
    if normalized == "free":
        return 0
    return -1


def _infer_tier_from_plan_name(plan_name: Optional[str]) -> Optional[str]:
    """Infer tier from plan name. Uses centralized tier inference."""
    from ...services.tier import infer_tier, Tier

    if not plan_name:
        return None
    tier = infer_tier(plan_name)
    # Return None for free tier to preserve existing behavior
    return tier.value if tier != Tier.FREE else None


async def _fetch_clerk_subscription_metadata(
    user_id: Optional[str], org_id: Optional[str]
) -> Dict[str, Optional[str]]:
    """
    Pull subscription metadata from Clerk for comparison.

    Returns {"user": tier?, "org": tier?}
    """
    secret = saas_settings.CLERK_SECRET_KEY or os.getenv("CLERK_SECRET_KEY")
    if not secret:
        return {"user": None, "org": None}

    headers = {"Authorization": f"Bearer {secret}"}
    base_url = "https://api.clerk.com/v1"
    results: Dict[str, Optional[str]] = {"user": None, "org": None}

    async with httpx.AsyncClient(timeout=5.0) as client:
        if user_id:
            try:
                resp = await client.get(f"{base_url}/users/{user_id}", headers=headers)
                if resp.is_success:
                    meta = resp.json().get("public_metadata") or {}
                    subscription = meta.get("subscription") or {}
                    results["user"] = subscription.get("tier") or subscription.get(
                        "plan"
                    )
            except Exception:
                pass

        if org_id:
            try:
                resp = await client.get(
                    f"{base_url}/organizations/{org_id}", headers=headers
                )
                if resp.is_success:
                    meta = resp.json().get("public_metadata") or {}
                    subscription = meta.get("subscription") or {}
                    results["org"] = subscription.get("tier") or subscription.get(
                        "plan"
                    )
            except Exception:
                pass

    return results


def _resolve_canonical_tier(
    candidates: Dict[str, Optional[str]]
) -> Tuple[str, str]:
    """
    Choose the highest-confidence tier across sources.

    Returns (tier, source_key).
    """
    best_tier = "free"
    best_source = "default"
    best_rank = _tier_rank(best_tier)

    for source, value in candidates.items():
        rank = _tier_rank(value)
        if rank > best_rank:
            best_rank = rank
            best_tier = _normalize_tier_value(value) or "free"
            best_source = source

    return best_tier, best_source


async def _build_usage_payload(
    db: AsyncSession,
    account_id: str,
    quota: Optional[UsageQuotaModel] = None,
    account: Optional[Account] = None,
    members: Optional[List[AccountMember]] = None,
    billing_interval: Optional[str] = None,
    entitlements: Optional[AccountEntitlements] = None,
) -> Dict[str, Any]:
    quota = quota or await _fetch_usage_quota(db, account_id)
    account = account or await _fetch_account(db, account_id)
    members = members or await _fetch_account_members(db, account_id)
    billing_interval = billing_interval or await _fetch_billing_interval(db, account_id)
    entitlements = entitlements or await _fetch_entitlements(db, account_id)

    active_members = sum(1 for m in members if (m.status or "").lower() == "active")
    invited_members = sum(
        1 for m in members if (m.status or "").lower() in {"invited", "pending"}
    )

    tier_value = (
        getattr(entitlements, "tier", None) if entitlements else None
    ) or getattr(quota, "tier", "free")
    if entitlements and entitlements.monthly_row_limit is not None:
        monthly_rows_limit = int(entitlements.monthly_row_limit)
    else:
        monthly_rows_limit = _coalesce_zero(
            getattr(quota, "monthly_match_entitlement", None)
        )
    monthly_rows_used = _coalesce_zero(getattr(quota, "monthly_rows_used", None))
    # Don't clamp to zero - allow negative values when over limit
    monthly_rows_remaining = (
        (monthly_rows_limit - monthly_rows_used) if monthly_rows_limit else None
    )

    monthly_enrichment_limit = _coalesce_zero(
        getattr(quota, "monthly_enrichment_entitlement", None)
    )
    enrichment_credits_used = _coalesce_zero(
        getattr(quota, "enrichment_credits_used", None)
    )
    monthly_enrichment_remaining = (
        max(monthly_enrichment_limit - enrichment_credits_used, 0)
        if monthly_enrichment_limit
        else None
    )

    # Get quota reset date
    monthly_reset_at = getattr(quota, "monthly_reset_at", None)
    if monthly_reset_at:
        reset_at_iso = monthly_reset_at.isoformat()
    else:
        # Default to first of next month if not set
        from datetime import datetime
        from dateutil.relativedelta import relativedelta

        reset_at_iso = (datetime.utcnow() + relativedelta(months=1, day=1)).isoformat()

    paid_seat_count = getattr(account, "paid_seat_count", 1) if account else 1
    total_in_use = active_members + invited_members

    # Check for seat overage
    seat_overage = total_in_use > paid_seat_count
    if seat_overage:
        log.warning(
            f"[USAGE] SEAT OVERAGE: account={account_id} "
            f"has {total_in_use} seats in use ({active_members} active + {invited_members} invited) "
            f"but only paying for {paid_seat_count}"
        )

    # Get addons from entitlements
    addons_list = getattr(entitlements, "addons", []) if entitlements else []
    byo_enabled = getattr(entitlements, "byo_enabled", False) if entitlements else False

    return {
        "account_id": account_id,
        "tier": tier_value,
        "subscription_status": getattr(quota, "subscription_status", None),
        "billing_interval": billing_interval,
        "monthly_rows_limit": monthly_rows_limit or None,
        "monthly_rows_used": monthly_rows_used,
        "monthly_rows_remaining": monthly_rows_remaining,
        "monthly_reset_at": reset_at_iso,
        "monthly_enrichment_limit": monthly_enrichment_limit or None,
        "enrichment_credits_used": enrichment_credits_used,
        "enrichment_credits_remaining": monthly_enrichment_remaining,
        "paid_seat_count": paid_seat_count,
        "active_seats": active_members,
        "invited_seats": invited_members,
        "seat_overage": seat_overage,
        "seat_overage_count": max(0, total_in_use - paid_seat_count),
        "addons": addons_list,
        "byo_enabled": byo_enabled,
        "updated_at": _now_iso(),
    }


@router.get("/usage")
async def get_account_usage(
    identity: Dict[str, Any] = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """Return high-level usage metrics for the authenticated account."""
    account_id = await _resolve_account_id(identity, db)
    return await _build_usage_payload(db, account_id)


async def _calculate_bucket_balance(db: AsyncSession, account_id: str) -> int:
    result = await db.execute(
        text(
            """
            SELECT COALESCE(SUM(quantity_remaining), 0)
            FROM enrichment_credit_buckets
            WHERE account_id = :account_id
              AND status = 'active'
            """
        ),
        {"account_id": account_id},
    )
    return int(result.scalar() or 0)


async def _build_credits_payload(
    db: AsyncSession,
    account_id: str,
    quota: Optional[UsageQuotaModel] = None,
    account: Optional[Account] = None,
) -> Dict[str, Any]:
    quota = quota or await _fetch_usage_quota(db, account_id)
    account = account or await _fetch_account(db, account_id)

    monthly_limit = _coalesce_zero(
        getattr(quota, "monthly_enrichment_entitlement", None)
    )
    monthly_used = _coalesce_zero(getattr(quota, "enrichment_credits_used", None))
    monthly_remaining = max(monthly_limit - monthly_used, 0) if monthly_limit else None

    purchased_credits = _coalesce_zero(getattr(account, "purchased_credits", None))
    active_bucket_credits = await _calculate_bucket_balance(db, account_id)

    # Fetch detailed transaction history from enrichment credit ledger
    # JOIN with buckets to get expiry dates for issue events
    transactions_result = await db.execute(
        select(
            EnrichmentCreditLedger.id,
            EnrichmentCreditLedger.occurred_at,
            EnrichmentCreditLedger.kind,
            EnrichmentCreditLedger.quantity,
            EnrichmentCreditLedger.balance_after,
            EnrichmentCreditLedger.bucket_id,
            EnrichmentCreditLedger.event_metadata,
            EnrichmentCreditBucket.source,
            EnrichmentCreditBucket.expires_at,
            EnrichmentCreditBucket.source_ref,
        )
        .select_from(EnrichmentCreditLedger)
        .outerjoin(
            EnrichmentCreditBucket,
            EnrichmentCreditLedger.bucket_id == EnrichmentCreditBucket.id,
        )
        .where(EnrichmentCreditLedger.account_id == account_id)
        .order_by(EnrichmentCreditLedger.occurred_at.desc())
        .limit(50)  # Show more transactions since we're showing individual buckets
    )

    transactions = []
    for row in transactions_result.all():
        txn = {
            "id": str(row.id),
            "source": row.source or "unknown",
            "event_type": row.kind,
            "quantity": row.quantity,
            "balance_after": row.balance_after,
            "recorded_at": row.occurred_at.isoformat() if row.occurred_at else None,
            "metadata": row.event_metadata or {},
        }
        # Include expiry date for issue events (bucket creations)
        if row.kind == "issue" and row.expires_at:
            txn["expires_at"] = (
                row.expires_at.isoformat()
                if hasattr(row.expires_at, "isoformat")
                else str(row.expires_at)
            )
        # Include stripe invoice ID from metadata if available
        if row.source_ref:
            txn["stripe_invoice_id"] = row.source_ref
        transactions.append(txn)

    # Credits are tracked in buckets, not in quota table's monthly_remaining
    # Don't double-count: active_bucket_credits already contains the monthly allocation
    total_available = active_bucket_credits + purchased_credits

    log.info(
        "[ACCOUNT_CREDITS] account=%s monthly_limit=%s monthly_used=%s monthly_remaining=%s "
        "active_bucket_credits=%s purchased_credits=%s total=%s",
        account_id,
        monthly_limit,
        monthly_used,
        monthly_remaining,
        active_bucket_credits,
        purchased_credits,
        total_available,
    )

    return {
        "account_id": account_id,
        # Frontend expects these fields
        "available": total_available,
        "total_balance": total_available,
        "breakdown": {
            "monthly_credits": active_bucket_credits,
            "purchased_credits": purchased_credits,
        },
        # Also include backend fields for compatibility
        "purchased_credits": purchased_credits,
        "active_bucket_credits": active_bucket_credits,
        "monthly_limit": monthly_limit or None,
        "monthly_used": monthly_used,
        "monthly_remaining": monthly_remaining,
        "total_available": total_available,
        "transactions": transactions,
        "last_monthly_reset": quota.tier_synced_at.isoformat()
        if (quota and quota.tier_synced_at)
        else None,
        "updated_at": _now_iso(),
    }


@router.get("/credits")
async def get_account_credits(
    identity: Dict[str, Any] = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """Return credit balances and recent credit transactions."""
    account_id = await _resolve_account_id(identity, db)
    return await _build_credits_payload(db, account_id)


def _serialize_subscription_row(row) -> Optional[Dict[str, Any]]:
    if not row:
        return None
    return {
        "stripe_subscription_id": row["stripe_subscription_id"],
        "status": row["status"],
        "seat_count": row["seat_count"],
        "plan_id": row["plan_id"],
        "plan_name": row["plan_name"],
        "billing_interval": row["billing_interval"],
        "current_period_start": row["current_period_start"].isoformat()
        if row["current_period_start"]
        else None,
        "current_period_end": row["current_period_end"].isoformat()
        if row["current_period_end"]
        else None,
        "trial_end": row["trial_end"].isoformat() if row["trial_end"] else None,
        "stripe_customer_id": row.get("stripe_customer_id"),
    }


async def _build_subscription_payload(
    db: AsyncSession, account_id: str
) -> Dict[str, Any]:
    subscription_query = text(
        """
        SELECT s.stripe_subscription_id,
               s.status,
               s.seat_count,
               s.plan_id,
               p.name AS plan_name,
               p.billing_interval,
               s.current_period_start,
               s.current_period_end,
               s.trial_end,
               a.stripe_customer_id
        FROM accounts a
        LEFT JOIN subscription s ON s.stripe_subscription_id = a.stripe_subscription_id
        LEFT JOIN plan p ON p.id = s.plan_id
        WHERE a.id = :account_id
        LIMIT 1
        """
    )
    subscription_row = (
        (await db.execute(subscription_query, {"account_id": account_id}))
        .mappings()
        .first()
    )

    events_result = await db.execute(
        select(SubscriptionEvent)
        .where(SubscriptionEvent.account_id == account_id)
        .order_by(SubscriptionEvent.occurred_at.desc())
        .limit(10)
    )
    events = [
        {
            "id": event.id,
            "event_type": event.event_type,
            "status": event.status,
            "previous_status": event.previous_status,
            "seat_delta": event.seat_delta,
            "seat_total": event.seat_total,
            "occurred_at": event.occurred_at.isoformat() if event.occurred_at else None,
            "metadata": event.event_metadata or {},
        }
        for event in events_result.scalars().all()
    ]

    seat_history_result = await db.execute(
        select(SeatTransaction)
        .where(SeatTransaction.account_id == account_id)
        .order_by(SeatTransaction.recorded_at.desc())
        .limit(10)
    )
    seat_history = [
        {
            "id": tx.id,
            "quantity_delta": tx.quantity_delta,
            "seat_total": tx.seat_total,
            "reason": tx.reason,
            "stripe_subscription_id": tx.stripe_subscription_id,
            "stripe_invoice_id": tx.stripe_invoice_id,
            "recorded_at": tx.recorded_at.isoformat() if tx.recorded_at else None,
            "metadata": tx.details or {},
        }
        for tx in seat_history_result.scalars().all()
    ]

    return {
        "account_id": account_id,
        "subscription": _serialize_subscription_row(subscription_row),
        "events": events,
        "seat_transactions": seat_history,
        "updated_at": _now_iso(),
    }


@router.get("/subscriptions")
async def get_account_subscription(
    identity: Dict[str, Any] = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """Return subscription snapshot, recent events, and seat transactions."""
    account_id = await _resolve_account_id(identity, db)
    return await _build_subscription_payload(db, account_id)


@router.get("/account/state")
async def get_account_state(
    include_sources: bool = False,
    identity: Dict[str, Any] = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """
    Canonical account state for dashboards.

    Returns the resolved tier, entitlements, and optional source breakdown.
    """
    account_id = await _resolve_account_id(identity, db)

    quota = await _fetch_usage_quota(db, account_id)
    account = await _fetch_account(db, account_id)
    members = await _fetch_account_members(db, account_id)
    billing_interval = await _fetch_billing_interval(db, account_id)
    entitlements = await _fetch_entitlements(db, account_id)

    usage_payload = await _build_usage_payload(
        db,
        account_id,
        quota=quota,
        account=account,
        members=members,
        billing_interval=billing_interval,
        entitlements=entitlements,
    )
    credits_payload = await _build_credits_payload(
        db, account_id, quota=quota, account=account
    )
    subscription_payload = await _build_subscription_payload(db, account_id)
    subscription = subscription_payload.get("subscription") or {}

    sources: Dict[str, Optional[str]] = {
        "usage_quota": getattr(quota, "tier", None),
        "subscription_plan": _infer_tier_from_plan_name(subscription.get("plan_name")),
        "entitlements": getattr(entitlements, "tier", None) if entitlements else None,
    }

    clerk_meta: Optional[Dict[str, Optional[str]]] = None
    if include_sources:
        clerk_meta = await _fetch_clerk_subscription_metadata(
            identity.get("user_id"), identity.get("org_id")
        )
        sources["clerk_org"] = clerk_meta.get("org") if clerk_meta else None
        sources["clerk_user"] = clerk_meta.get("user") if clerk_meta else None

    resolved_tier, tier_source = _resolve_canonical_tier(sources)

    response: Dict[str, Any] = {
        "account_id": account_id,
        "tier": resolved_tier,
        "tier_source": tier_source,
        "subscription_status": getattr(quota, "subscription_status", None)
        or subscription.get("status"),
        "billing_interval": billing_interval or subscription.get("billing_interval"),
        "rows": {
            "limit": usage_payload.get("monthly_rows_limit"),
            "used": usage_payload.get("monthly_rows_used"),
            "remaining": usage_payload.get("monthly_rows_remaining"),
            "resets_at": usage_payload.get("monthly_reset_at"),
        },
        "enrichments": {
            "monthly_limit": usage_payload.get("monthly_enrichment_limit"),
            "used": usage_payload.get("enrichment_credits_used"),
            "remaining": usage_payload.get("enrichment_credits_remaining"),
            "buckets_available": credits_payload.get("active_bucket_credits"),
            "purchased": credits_payload.get("purchased_credits"),
            "total_available": credits_payload.get("total_available"),
        },
        "seats": {
            "paid": usage_payload.get("paid_seat_count"),
            "active": usage_payload.get("active_seats"),
            "invited": usage_payload.get("invited_seats"),
            "overage": usage_payload.get("seat_overage"),
            "overage_count": usage_payload.get("seat_overage_count"),
        },
        "updated_at": _now_iso(),
    }

    if include_sources:
        response["sources"] = {
            **sources,
            "plan_name": subscription.get("plan_name"),
            "subscription_status": subscription.get("status"),
            "clerk": clerk_meta,
        }

    return response


@router.get("/dashboard")
async def get_dashboard_snapshot(
    identity: Dict[str, Any] = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """Return consolidated dashboard data in a single response."""
    account_id = await _resolve_account_id(identity, db)

    quota = await _fetch_usage_quota(db, account_id)
    account = await _fetch_account(db, account_id)
    members = await _fetch_account_members(db, account_id)
    billing_interval = await _fetch_billing_interval(db, account_id)
    entitlements = await _fetch_entitlements(db, account_id)

    usage_payload = await _build_usage_payload(
        db,
        account_id,
        quota=quota,
        account=account,
        members=members,
        billing_interval=billing_interval,
        entitlements=entitlements,
    )
    credits_payload = await _build_credits_payload(
        db, account_id, quota=quota, account=account
    )
    subscription_payload = await _build_subscription_payload(db, account_id)

    return {
        "account_id": account_id,
        "usage": usage_payload,
        "credits": credits_payload,
        "subscription": subscription_payload,
        "updated_at": _now_iso(),
    }


async def _build_capabilities_payload(
    db: AsyncSession,
    account_id: str,
) -> Dict[str, Any]:
    gateway = UsageGateway(db)
    capabilities = await gateway.get_capabilities(account_id)

    # Fetch quota for reset dates
    quota = await _fetch_usage_quota(db, account_id)
    monthly_reset_at = getattr(quota, "monthly_reset_at", None)
    if monthly_reset_at:
        reset_at_iso = monthly_reset_at.isoformat()
    else:
        from dateutil.relativedelta import relativedelta

        reset_at_iso = (
            datetime.now(timezone.utc) + relativedelta(months=1, day=1)
        ).isoformat()

    # Calculate daily reset time (midnight UTC tomorrow)
    from datetime import timedelta

    now = datetime.now(timezone.utc)
    tomorrow = (now + timedelta(days=1)).replace(
        hour=0, minute=0, second=0, microsecond=0
    )
    daily_reset_at = tomorrow.isoformat()

    # Extract usage data from capabilities
    gremlin = capabilities.get("gremlin", {})
    byo = capabilities.get("byo", {})
    list_builder = capabilities.get("list_builder", {})
    rows = capabilities.get("rows", {})
    webhooks = capabilities.get("webhooks", {})

    tier_value = capabilities.get("tier", "free")
    addons = capabilities.get("addons", []) or []
    try:
        tier_config = get_tier_config(tier_value)
    except Exception:
        tier_config = None

    byo_providers: List[str] = []
    byo_bundled = False
    if tier_config:
        byo_bundled = bool(getattr(tier_config, "byo_bundled", False))
        byo_providers.extend(getattr(tier_config, "byo_providers", []) or [])
    if addons and ENRICHMENT_CONFIG and BYO_ADDON_KEY in addons:
        try:
            addon_cfg = ENRICHMENT_CONFIG.get_addon(BYO_ADDON_KEY)
            byo_providers.extend(addon_cfg.providers or [])
        except Exception:
            pass
    byo_providers = sorted({p for p in byo_providers if p})

    return {
        "account_id": account_id,
        "tier": tier_value,
        "addons": addons,
        "rows": {
            "limit": rows.get("limit", 0),
            "used": rows.get("used", 0),
            "remaining": rows.get("remaining", 0),
            "reset_at": rows.get("resets_at") or reset_at_iso,
            "period": "monthly",
        },
        "gremlin": {
            "enabled": gremlin.get("enabled", False),
            "daily_limit": gremlin.get("limit", gremlin.get("daily_limit", 0)),
            "used_today": gremlin.get("used", gremlin.get("used_today", 0)),
            "remaining_today": gremlin.get(
                "remaining", gremlin.get("remaining_today", 0)
            ),
            "reset_at": gremlin.get("resets_at", daily_reset_at),
        },
        "byo": {
            "enabled": byo.get("enabled", False),
            "bundled": byo_bundled,
            "daily_limit": byo.get("daily_limit", 0),
            "used_today": byo.get("daily_used", byo.get("used_today", 0)),
            "remaining_today": byo.get(
                "daily_remaining", byo.get("remaining_today", 0)
            ),
            "providers": byo_providers,
            "reset_at": byo.get("resets_at", daily_reset_at),
        },
        "list_builder": {
            "daily_limit": list_builder.get("daily_limit", 0),
            "used_today": list_builder.get(
                "daily_used", list_builder.get("used_today", 0)
            ),
            "remaining_today": list_builder.get(
                "daily_remaining", list_builder.get("remaining_today", 0)
            ),
            "monthly_limit": list_builder.get("monthly_limit", 0),
            "used_this_month": list_builder.get(
                "monthly_used", list_builder.get("used_this_month", 0)
            ),
            "remaining_this_month": list_builder.get(
                "monthly_remaining", list_builder.get("remaining_this_month", 0)
            ),
            "daily_reset_at": list_builder.get("daily_resets_at", daily_reset_at),
            "monthly_reset_at": list_builder.get("monthly_resets_at", reset_at_iso),
        },
        "ai_report": {
            "row_cap": capabilities.get("ai_report_row_cap", 0),
        },
        "webhooks": {
            "rows_limit": webhooks.get("limit"),
            "rows_used": webhooks.get("used", 0),
            "rows_remaining": webhooks.get("remaining"),
            "rows_reset_at": webhooks.get("resets_at", reset_at_iso),
            "max_active": webhooks.get("max_active"),
        },
        "updated_at": _now_iso(),
    }


def _serialize_tier_config(tier: Any) -> Dict[str, Any]:
    return tier.dict(exclude_none=True)


def _serialize_addon_config(addon: Any) -> Dict[str, Any]:
    return addon.dict(exclude_none=True)


def _serialize_pack_config(pack: Any) -> Dict[str, Any]:
    return pack.dict(exclude_none=True)


def _build_tier_config_payload() -> Dict[str, Any]:
    if not ENRICHMENT_CONFIG:
        raise HTTPException(
            status_code=500,
            detail="Tier limits config is not available. Check config/tier_limits.yaml",
        )

    tiers: Dict[str, Any] = {}
    for name, tier in ENRICHMENT_CONFIG.tiers.items():
        if name.startswith("_"):
            continue
        tiers[name] = _serialize_tier_config(tier)

    addons: Dict[str, Any] = {}
    for addon_key, addon in (ENRICHMENT_CONFIG.addons or {}).items():
        addons[addon_key] = _serialize_addon_config(addon)

    packs: Dict[str, Any] = {}
    for pack_key, pack in (ENRICHMENT_CONFIG.enrichment_packs or {}).items():
        packs[pack_key] = _serialize_pack_config(pack)

    payload: Dict[str, Any] = {
        "tiers": tiers,
        "tier_limits": get_tier_limits(),
        "addons": addons,
        "enrichment_packs": packs,
    }

    if getattr(ENRICHMENT_CONFIG, "rollover", None):
        payload["rollover"] = ENRICHMENT_CONFIG.rollover.dict(exclude_none=True)
    if getattr(ENRICHMENT_CONFIG, "features", None):
        payload["features"] = ENRICHMENT_CONFIG.features.dict(exclude_none=True)

    return payload


async def _fetch_trial_usage_counts(
    db: AsyncSession,
    account_id: str,
) -> Dict[str, int]:
    counts: Dict[str, int] = {}

    gremlin_total = await db.scalar(
        select(func.coalesce(func.sum(UsageLedger.amount), 0)).where(
            UsageLedger.account_id == account_id,
            UsageLedger.resource == "gremlin",
        )
    )
    counts["gremlin_chat"] = int(gremlin_total or 0)

    ai_report_total = await db.scalar(
        select(func.count(SalesforceAIReport.id)).where(
            SalesforceAIReport.account_id == str(account_id)
        )
    )
    counts["salesforce_ai_report"] = int(ai_report_total or 0)

    return counts


def _build_trial_payload(
    tier: str,
    usage_counts: Dict[str, int],
) -> Dict[str, Any]:
    if not CAPABILITIES_CONFIG:
        return {
            "tracking_mode": "lifetime",
            "exhausted_error_code": "TRIAL_EXHAUSTED",
            "default_upgrade_cta": "You've reached your free trial limit. Upgrade to continue.",
            "capabilities": [],
        }

    trial_caps: List[Dict[str, Any]] = []
    for cap in CAPABILITIES_CONFIG.capabilities.values():
        if cap.shelved:
            continue
        tier_data = cap.tiers.get(tier)
        if not tier_data or tier_data.state != "trial":
            continue
        trial_period = tier_data.trial_period or CAPABILITIES_CONFIG.trial_config.tracking_mode
        used = usage_counts.get(cap.id)
        trial_caps.append(
            {
                "id": cap.id,
                "label": cap.label,
                "group": cap.group,
                "trial_uses": tier_data.trial_uses,
                "trial_period": trial_period,
                "upgrade_cta": tier_data.upgrade_cta
                or CAPABILITIES_CONFIG.trial_config.default_upgrade_cta,
                "used": used,
                "tracked": cap.id in usage_counts,
            }
        )

    return {
        "tracking_mode": CAPABILITIES_CONFIG.trial_config.tracking_mode,
        "exhausted_error_code": CAPABILITIES_CONFIG.trial_config.exhausted_error_code,
        "default_upgrade_cta": CAPABILITIES_CONFIG.trial_config.default_upgrade_cta,
        "capabilities": trial_caps,
    }


@router.get("/me/capabilities")
async def get_my_capabilities(
    identity: Dict[str, Any] = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
):
    """
    Return current user's capabilities and usage from the UsageGateway.

    This is the single source of truth for "what can this account do?"
    and "how much have they used?" - used by the My Account dialog.
    """
    account_id = await _resolve_account_id(identity, db)
    return await _build_capabilities_payload(db, account_id)


@router.get("/public/tier-config")
async def get_public_tier_config() -> Dict[str, Any]:
    """Return public tier configuration for pricing pages."""
    payload = _build_tier_config_payload()
    payload["updated_at"] = _now_iso()
    return payload


@router.get("/me/tier-config")
async def get_my_tier_config(
    identity: Dict[str, Any] = Depends(get_current_identity),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """Return tier config, usage, and trial snapshot for the authenticated account."""
    account_id = await _resolve_account_id(identity, db)

    quota = await _fetch_usage_quota(db, account_id)
    entitlements = await _fetch_entitlements(db, account_id)
    billing_interval = await _fetch_billing_interval(db, account_id)
    subscription_payload = await _build_subscription_payload(db, account_id)
    subscription = subscription_payload.get("subscription") or {}

    sources: Dict[str, Optional[str]] = {
        "usage_quota": getattr(quota, "tier", None),
        "subscription_plan": _infer_tier_from_plan_name(subscription.get("plan_name")),
        "entitlements": getattr(entitlements, "tier", None) if entitlements else None,
    }
    resolved_tier, tier_source = _resolve_canonical_tier(sources)

    usage_payload = await _build_capabilities_payload(db, account_id)
    trial_usage_counts = await _fetch_trial_usage_counts(db, account_id)
    trial_payload = _build_trial_payload(resolved_tier, trial_usage_counts)

    payload = _build_tier_config_payload()
    payload.update(
        {
            "account_id": account_id,
            "tier": resolved_tier,
            "tier_source": tier_source,
            "billing_interval": billing_interval or subscription.get("billing_interval"),
            "usage": usage_payload,
            "trial": trial_payload,
            "updated_at": _now_iso(),
        }
    )
    return payload
