"""Usage gateway for centralized entitlement checks and consumption."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import date, datetime, timedelta, timezone
from typing import Any, Dict, Iterable, List, Optional

import logging
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session

from ..config.tier_limits import (
    TIER_CONFIG as ENRICHMENT_CONFIG,
    get_tier_config,
    normalize_tier_name,
)
from .. import settings as saas_settings
from ..models import (
    AccountEntitlements,
    AccountUsage,
    UsageLedger,
    UsageQuotaModel,
    utcnow,
)

logger = logging.getLogger(__name__)

BYO_ADDON_KEY = "byo_provider_locker"


@dataclass
class ConsumeResult:
    allowed: bool
    resource: str
    tier: str
    used: int
    limit: Optional[int]
    remaining: Optional[int]
    reset_at: Optional[datetime]
    message: Optional[str] = None
    idempotent: bool = False


def _today_utc() -> date:
    return datetime.now(timezone.utc).date()


def _month_start(day: date) -> date:
    return date(day.year, day.month, 1)


def _next_day_start(day: date) -> datetime:
    return datetime.combine(day + timedelta(days=1), datetime.min.time(), tzinfo=timezone.utc)


def _next_month_start(day: date) -> datetime:
    if day.month == 12:
        return datetime(day.year + 1, 1, 1, tzinfo=timezone.utc)
    return datetime(day.year, day.month + 1, 1, tzinfo=timezone.utc)


def _normalize_tier(value: Optional[str]) -> str:
    return normalize_tier_name(value)


def _normalize_addons(addons: Optional[Iterable[str]]) -> List[str]:
    return sorted({str(addon).strip().lower() for addon in (addons or []) if addon})


def _safe_tier_config(tier: str):
    try:
        return get_tier_config(tier)
    except Exception as exc:  # noqa: BLE001 - defensive fallback
        logger.warning("Falling back to free tier config for %s: %s", tier, exc)
        return get_tier_config("free")


def _addon_limits_for_tier(addon_key: str, tier: str):
    if not ENRICHMENT_CONFIG:
        return None
    try:
        return ENRICHMENT_CONFIG.get_addon_limits(addon_key, tier)
    except Exception:  # noqa: BLE001 - defensive fallback
        return None


def _derive_byo_limits(tier: str, addons: List[str]) -> tuple[bool, int]:
    tier_key = _normalize_tier(tier)
    tier_config = _safe_tier_config(tier_key)

    byo_enabled = bool(tier_config.byo_enrichment_enabled)
    byo_daily_limit = int(tier_config.byo_daily_enrichment_rows or 0)

    if BYO_ADDON_KEY in addons:
        addon_limits = _addon_limits_for_tier(BYO_ADDON_KEY, tier_key)
        if addon_limits:
            byo_enabled = True
            if addon_limits.daily_enrichment_rows is not None:
                byo_daily_limit = int(addon_limits.daily_enrichment_rows)

    return byo_enabled, byo_daily_limit


def _derive_entitlements(
    tier: str,
    addons: List[str],
    monthly_row_limit: Optional[int] = None,
) -> Dict[str, Any]:
    tier_key = _normalize_tier(tier)
    tier_config = _safe_tier_config(tier_key)

    gremlin_daily_limit = int(tier_config.gremlin_messages_per_day or 0)
    ai_report_row_cap = int(tier_config.ai_report_row_cap or 0)
    monthly_rows = int(monthly_row_limit or tier_config.monthly_rows)
    addons_norm = _normalize_addons(addons)
    byo_enabled, byo_daily_limit = _derive_byo_limits(tier_key, addons_norm)

    return {
        "tier": tier_key,
        "addons": addons_norm,
        "monthly_row_limit": monthly_rows,
        "gremlin_daily_limit": gremlin_daily_limit,
        "ai_report_row_cap": ai_report_row_cap,
        "byo_enabled": byo_enabled,
        "byo_daily_limit": int(byo_daily_limit or 0),
    }


class UsageGateway:
    """Single source of truth for account capabilities and consumption."""

    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    async def get_capabilities(self, account_id: str) -> Dict[str, Any]:
        entitlements = await self._get_or_create_entitlements(account_id)
        usage = await self._get_or_create_usage(account_id)
        await self._apply_usage_resets(usage)

        quota = await self._fetch_usage_quota(account_id)
        tier_config = _safe_tier_config(entitlements.tier)

        rows_used = (
            int(quota.monthly_rows_used or 0)
            if quota and quota.monthly_rows_used is not None
            else int(usage.rows_used or 0)
        )
        rows_limit = int(entitlements.monthly_row_limit or tier_config.monthly_rows)
        rows_remaining = max(rows_limit - rows_used, 0) if rows_limit else None

        gremlin_used = int(usage.gremlin_messages_used or 0)
        gremlin_limit = int(entitlements.gremlin_daily_limit or 0)
        gremlin_remaining = (
            max(gremlin_limit - gremlin_used, 0) if gremlin_limit else None
        )

        byo_used = int(usage.byo_rows_used or 0)
        byo_limit = int(entitlements.byo_daily_limit or 0)
        byo_remaining = max(byo_limit - byo_used, 0) if byo_limit else None

        list_builder_daily_used = (
            int(quota.list_builder_daily_exports or 0)
            if quota and quota.list_builder_daily_exports is not None
            else int(usage.list_builder_daily_used or 0)
        )
        list_builder_monthly_used = (
            int(quota.list_builder_monthly_exports or 0)
            if quota and quota.list_builder_monthly_exports is not None
            else int(usage.list_builder_monthly_used or 0)
        )
        webhook_rows_used = int(usage.webhook_rows_used or 0)
        webhook_rows_limit_raw = int(tier_config.webhook_rows_per_month or 0)
        webhook_rows_limit = webhook_rows_limit_raw or None
        webhook_rows_remaining = (
            max(webhook_rows_limit_raw - webhook_rows_used, 0)
            if webhook_rows_limit_raw
            else None
        )
        max_active_webhooks_raw = int(tier_config.max_sheet_webhooks or 0)
        max_active_webhooks = max_active_webhooks_raw or None

        features = []
        if tier_config.allow_advanced_algos:
            features.append("advanced_algos")
        if tier_config.gremlin_enabled and gremlin_limit > 0:
            features.append("gremlin_chat")
        if entitlements.byo_enabled:
            features.append("byo_enrichment")

        today = _today_utc()

        return {
            "tier": entitlements.tier,
            "addons": entitlements.addons or [],
            "rows": {
                "used": rows_used,
                "limit": rows_limit,
                "remaining": rows_remaining,
                "resets_at": _next_month_start(today).isoformat(),
            },
            "gremlin": {
                "used": gremlin_used,
                "limit": gremlin_limit,
                "remaining": gremlin_remaining,
                "resets_at": _next_day_start(today).isoformat(),
                "enabled": tier_config.gremlin_enabled and gremlin_limit > 0,
            },
            "byo": {
                "enabled": bool(entitlements.byo_enabled),
                "daily_used": byo_used,
                "daily_limit": byo_limit,
                "daily_remaining": byo_remaining,
                "resets_at": _next_day_start(today).isoformat(),
            },
            "list_builder": {
                "daily_used": list_builder_daily_used,
                "daily_limit": tier_config.list_builder_daily_rows,
                "daily_remaining": max(
                    tier_config.list_builder_daily_rows - list_builder_daily_used, 0
                ),
                "monthly_used": list_builder_monthly_used,
                "monthly_limit": tier_config.list_builder_monthly_rows,
                "monthly_remaining": max(
                    tier_config.list_builder_monthly_rows - list_builder_monthly_used, 0
                ),
                "daily_resets_at": _next_day_start(today).isoformat(),
                "monthly_resets_at": _next_month_start(today).isoformat(),
            },
            "webhooks": {
                "used": webhook_rows_used,
                "limit": webhook_rows_limit,
                "remaining": webhook_rows_remaining,
                "resets_at": _next_month_start(today).isoformat(),
                "max_active": max_active_webhooks,
            },
            "ai_report_row_cap": int(entitlements.ai_report_row_cap or 0),
            "features": features,
        }

    async def get_webhook_limits(self, account_id: str) -> Dict[str, Any]:
        """Return webhook tier limits and current usage for an account."""
        entitlements = await self._get_or_create_entitlements(account_id)
        usage = await self._get_or_create_usage(account_id)
        await self._apply_usage_resets(usage)

        tier_config = _safe_tier_config(entitlements.tier)
        webhook_rows_limit_raw = int(tier_config.webhook_rows_per_month or 0)
        webhook_rows_limit = webhook_rows_limit_raw or None
        webhook_rows_used = int(usage.webhook_rows_used or 0)
        max_webhooks_raw = int(tier_config.max_sheet_webhooks or 0)
        max_webhooks = max_webhooks_raw or None

        return {
            "tier": entitlements.tier,
            "max_webhooks": max_webhooks,
            "webhook_rows_per_month": webhook_rows_limit,
            "webhook_rows_used": webhook_rows_used,
            "webhook_rows_remaining": (
                max(webhook_rows_limit_raw - webhook_rows_used, 0)
                if webhook_rows_limit_raw
                else None
            ),
            "history_retention_days": int(
                tier_config.webhook_history_retention_days or 1
            ),
        }

    async def check_and_consume(
        self,
        account_id: str,
        resource: str,
        amount: int,
        *,
        source: Optional[str] = None,
        idempotency_key: Optional[str] = None,
    ) -> ConsumeResult:
        if amount <= 0:
            tier = await self._get_existing_tier(account_id)
            return ConsumeResult(
                allowed=True,
                resource=resource,
                tier=tier,
                used=0,
                limit=None,
                remaining=None,
                reset_at=None,
                message="No consumption required.",
            )

        if self.db.in_transaction():
            return await self._check_and_consume_in_transaction(
                account_id=account_id,
                resource=resource,
                amount=amount,
                source=source,
                idempotency_key=idempotency_key,
            )

        async with self.db.begin():
            return await self._check_and_consume_in_transaction(
                account_id=account_id,
                resource=resource,
                amount=amount,
                source=source,
                idempotency_key=idempotency_key,
            )

    async def sync_from_stripe(
        self,
        account_id: str,
        *,
        tier: str,
        addons: Optional[List[str]] = None,
        source: str = "stripe",
        monthly_row_limit: Optional[int] = None,
        expires_at: Optional[datetime] = None,
    ) -> AccountEntitlements:
        entitlements = _derive_entitlements(
            tier=tier,
            addons=addons or [],
            monthly_row_limit=monthly_row_limit,
        )

        stmt = select(AccountEntitlements).where(
            AccountEntitlements.account_id == account_id
        )
        result = await self.db.execute(stmt)
        existing = result.scalar_one_or_none()

        if existing:
            for key, value in entitlements.items():
                setattr(existing, key, value)
            existing.source = source
            existing.synced_at = utcnow()
            existing.expires_at = expires_at
            self.db.add(existing)
            await self.db.commit()
            await self.db.refresh(existing)
            return existing

        new_row = AccountEntitlements(
            account_id=account_id,
            source=source,
            synced_at=utcnow(),
            expires_at=expires_at,
            **entitlements,
        )
        self.db.add(new_row)
        await self.db.commit()
        await self.db.refresh(new_row)
        return new_row

    async def admin_override(
        self,
        account_id: str,
        *,
        changes: Dict[str, Any],
        reason: str,
        admin_user_id: str,
        admin_email: Optional[str] = None,
    ) -> AccountEntitlements:
        entitlements = await self._get_or_create_entitlements(account_id)
        for key, value in changes.items():
            if hasattr(entitlements, key):
                setattr(entitlements, key, value)
        entitlements.source = "admin_override"
        entitlements.synced_at = utcnow()
        self.db.add(entitlements)
        await self.db.commit()
        await self.db.refresh(entitlements)

        try:
            from ..models import AdminAction

            if admin_email:
                self.db.add(
                    AdminAction(
                        admin_user_id=admin_user_id,
                        admin_email=admin_email,
                        action="usage_gateway_override",
                        target_account_id=account_id,
                        details={"reason": reason, "changes": changes},
                    )
                )
                await self.db.commit()
        except Exception:
            await self.db.rollback()

        return entitlements

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

    async def _get_or_create_entitlements(self, account_id: str) -> AccountEntitlements:
        stmt = select(AccountEntitlements).where(
            AccountEntitlements.account_id == account_id
        )
        result = await self.db.execute(stmt)
        entitlements = result.scalar_one_or_none()
        if entitlements:
            # Backfill trial window if missing (free tier only).
            if _normalize_tier(entitlements.tier) == "free":
                now = utcnow()
                changed = False
                if entitlements.trial_started_at is None:
                    if entitlements.expires_at:
                        entitlements.trial_started_at = entitlements.expires_at - timedelta(
                            days=saas_settings.TRIAL_LENGTH_DAYS
                        )
                    elif entitlements.synced_at:
                        entitlements.trial_started_at = entitlements.synced_at
                    else:
                        entitlements.trial_started_at = now
                    changed = True
                if entitlements.expires_at is None:
                    entitlements.expires_at = (
                        entitlements.trial_started_at or now
                    ) + timedelta(days=saas_settings.TRIAL_LENGTH_DAYS)
                    changed = True
                if changed:
                    self.db.add(entitlements)
                    if self.db.in_transaction():
                        await self.db.flush()
                    else:
                        await self.db.commit()
                        await self.db.refresh(entitlements)
            return entitlements

        quota = await self._fetch_usage_quota(account_id)
        tier = _normalize_tier(quota.tier if quota else "free")
        monthly_row_limit = (
            int(quota.monthly_match_entitlement)
            if quota and quota.monthly_match_entitlement is not None
            else None
        )

        derived = _derive_entitlements(
            tier=tier,
            addons=[],
            monthly_row_limit=monthly_row_limit,
        )
        now = utcnow()
        trial_started_at = None
        expires_at = None
        if derived.get("tier") == "free":
            trial_started_at = now
            expires_at = now + timedelta(days=saas_settings.TRIAL_LENGTH_DAYS)
        entitlements = AccountEntitlements(
            account_id=account_id,
            source="usage_quota_bootstrap",
            synced_at=now,
            expires_at=expires_at,
            trial_started_at=trial_started_at,
            **derived,
        )
        self.db.add(entitlements)
        if self.db.in_transaction():
            await self.db.flush()
        else:
            await self.db.commit()
            await self.db.refresh(entitlements)
        return entitlements

    async def _get_or_create_usage(
        self, account_id: str, *, for_update: bool = False
    ) -> AccountUsage:
        stmt = select(AccountUsage).where(AccountUsage.account_id == account_id)
        if for_update:
            stmt = stmt.with_for_update()
        result = await self.db.execute(stmt)
        usage = result.scalar_one_or_none()
        if usage:
            return usage

        today = _today_utc()
        month_start = _month_start(today)
        usage = AccountUsage(
            account_id=account_id,
            rows_used=0,
            rows_period_start=month_start,
            gremlin_messages_used=0,
            gremlin_period_start=today,
            byo_rows_used=0,
            byo_period_start=today,
            list_builder_daily_used=0,
            list_builder_monthly_used=0,
            list_builder_daily_start=today,
            list_builder_monthly_start=month_start,
            webhook_rows_used=0,
            webhook_rows_period_start=month_start,
            updated_at=utcnow(),
        )
        self.db.add(usage)
        if self.db.in_transaction():
            await self.db.flush()
        else:
            await self.db.commit()
            await self.db.refresh(usage)
        return usage

    async def _get_existing_tier(self, account_id: str) -> str:
        stmt = select(AccountEntitlements.tier).where(
            AccountEntitlements.account_id == account_id
        )
        result = await self.db.execute(stmt)
        tier = result.scalar_one_or_none()
        if tier:
            return _normalize_tier(tier)
        quota = await self._fetch_usage_quota(account_id)
        if quota and quota.tier:
            return _normalize_tier(quota.tier)
        return "free"

    async def _check_and_consume_in_transaction(
        self,
        *,
        account_id: str,
        resource: str,
        amount: int,
        source: Optional[str],
        idempotency_key: Optional[str],
    ) -> ConsumeResult:
        entitlements = await self._get_or_create_entitlements(account_id)
        usage = await self._get_or_create_usage(account_id, for_update=True)
        self._reset_usage_in_place(usage)

        if idempotency_key:
            existing = await self._get_ledger_by_key(idempotency_key)
            if existing:
                remaining = None
                if existing.balance_after is not None and existing.balance_after >= 0:
                    remaining = int(existing.balance_after)
                return ConsumeResult(
                    allowed=True,
                    resource=resource,
                    tier=entitlements.tier,
                    used=amount,
                    limit=None,
                    remaining=remaining,
                    reset_at=None,
                    message="Idempotent replay.",
                    idempotent=True,
                )

        result = self._evaluate_resource(
            entitlements=entitlements,
            usage=usage,
            resource=resource,
            amount=amount,
        )

        if not result.allowed:
            return result

        self._apply_consumption(usage, resource, amount)
        usage.updated_at = utcnow()
        self.db.add(usage)

        balance_after = result.remaining if result.remaining is not None else 0
        ledger_entry = UsageLedger(
            account_id=account_id,
            resource=resource,
            amount=amount,
            balance_after=int(balance_after),
            source=source,
            idempotency_key=idempotency_key,
            created_at=utcnow(),
        )
        self.db.add(ledger_entry)

        return result

    async def _apply_usage_resets(self, usage: AccountUsage) -> None:
        if self._reset_usage_in_place(usage):
            usage.updated_at = utcnow()
            self.db.add(usage)
            await self.db.commit()
            await self.db.refresh(usage)

    def _reset_usage_in_place(self, usage: AccountUsage) -> bool:
        today = _today_utc()
        month_start = _month_start(today)
        updated = False

        if usage.rows_period_start is None or usage.rows_period_start < month_start:
            usage.rows_used = 0
            usage.rows_period_start = month_start
            updated = True

        if usage.gremlin_period_start is None or usage.gremlin_period_start < today:
            usage.gremlin_messages_used = 0
            usage.gremlin_period_start = today
            updated = True

        if usage.byo_period_start is None or usage.byo_period_start < today:
            usage.byo_rows_used = 0
            usage.byo_period_start = today
            updated = True

        if (
            usage.list_builder_daily_start is None
            or usage.list_builder_daily_start < today
        ):
            usage.list_builder_daily_used = 0
            usage.list_builder_daily_start = today
            updated = True

        if (
            usage.list_builder_monthly_start is None
            or usage.list_builder_monthly_start < month_start
        ):
            usage.list_builder_monthly_used = 0
            usage.list_builder_monthly_start = month_start
            updated = True

        if (
            usage.webhook_rows_period_start is None
            or usage.webhook_rows_period_start < month_start
        ):
            usage.webhook_rows_used = 0
            usage.webhook_rows_period_start = month_start
            updated = True

        return updated

    async def _get_ledger_by_key(self, key: str) -> Optional[UsageLedger]:
        stmt = select(UsageLedger).where(UsageLedger.idempotency_key == key)
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    def _evaluate_resource(
        self,
        *,
        entitlements: AccountEntitlements,
        usage: AccountUsage,
        resource: str,
        amount: int,
    ) -> ConsumeResult:
        tier = entitlements.tier
        resource_key = resource.lower()
        today = _today_utc()

        if resource_key == "gremlin":
            limit = int(entitlements.gremlin_daily_limit or 0)
            used = int(usage.gremlin_messages_used or 0)
            if limit <= 0:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=used,
                    limit=limit,
                    remaining=0,
                    reset_at=_next_day_start(today),
                    message="Gremlin chat is not enabled for this tier.",
                )
            if used + amount > limit:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=used,
                    limit=limit,
                    remaining=max(limit - used, 0),
                    reset_at=_next_day_start(today),
                    message="Gremlin daily message limit reached.",
                )
            remaining = max(limit - (used + amount), 0)
            return ConsumeResult(
                allowed=True,
                resource=resource_key,
                tier=tier,
                used=used + amount,
                limit=limit,
                remaining=remaining,
                reset_at=_next_day_start(today),
            )

        if resource_key == "byo_rows":
            if not entitlements.byo_enabled:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=int(usage.byo_rows_used or 0),
                    limit=int(entitlements.byo_daily_limit or 0),
                    remaining=0,
                    reset_at=_next_day_start(today),
                    message="BYO enrichment is not enabled for this account.",
                )
            limit = int(entitlements.byo_daily_limit or 0)
            used = int(usage.byo_rows_used or 0)
            if limit <= 0:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=used,
                    limit=limit,
                    remaining=0,
                    reset_at=_next_day_start(today),
                    message="BYO daily limit not configured.",
                )
            if used + amount > limit:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=used,
                    limit=limit,
                    remaining=max(limit - used, 0),
                    reset_at=_next_day_start(today),
                    message="BYO daily row limit reached.",
                )
            remaining = max(limit - (used + amount), 0)
            return ConsumeResult(
                allowed=True,
                resource=resource_key,
                tier=tier,
                used=used + amount,
                limit=limit,
                remaining=remaining,
                reset_at=_next_day_start(today),
            )

        if resource_key == "list_builder":
            tier_config = _safe_tier_config(tier)
            daily_limit = int(tier_config.list_builder_daily_rows or 0)
            monthly_limit = int(tier_config.list_builder_monthly_rows or 0)
            daily_used = int(usage.list_builder_daily_used or 0)
            monthly_used = int(usage.list_builder_monthly_used or 0)
            if daily_limit and daily_used + amount > daily_limit:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=daily_used,
                    limit=daily_limit,
                    remaining=max(daily_limit - daily_used, 0),
                    reset_at=_next_day_start(today),
                    message="List builder daily limit reached.",
                )
            if monthly_limit and monthly_used + amount > monthly_limit:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=monthly_used,
                    limit=monthly_limit,
                    remaining=max(monthly_limit - monthly_used, 0),
                    reset_at=_next_month_start(today),
                    message="List builder monthly limit reached.",
                )
            remaining = None
            if daily_limit:
                remaining = max(daily_limit - (daily_used + amount), 0)
            return ConsumeResult(
                allowed=True,
                resource=resource_key,
                tier=tier,
                used=monthly_used + amount,
                limit=monthly_limit,
                remaining=remaining,
                reset_at=_next_day_start(today),
            )

        if resource_key == "rows":
            limit = int(entitlements.monthly_row_limit or 0)
            used = int(usage.rows_used or 0)
            if limit and used + amount > limit:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=used,
                    limit=limit,
                    remaining=max(limit - used, 0),
                    reset_at=_next_month_start(today),
                    message="Monthly row limit reached.",
                )
            remaining = max(limit - (used + amount), 0) if limit else None
            return ConsumeResult(
                allowed=True,
                resource=resource_key,
                tier=tier,
                used=used + amount,
                limit=limit,
                remaining=remaining,
                reset_at=_next_month_start(today),
            )

        if resource_key == "webhook_rows":
            tier_config = _safe_tier_config(tier)
            limit = int(tier_config.webhook_rows_per_month or 0)
            used = int(usage.webhook_rows_used or 0)
            if limit and used + amount > limit:
                return ConsumeResult(
                    allowed=False,
                    resource=resource_key,
                    tier=tier,
                    used=used,
                    limit=limit,
                    remaining=max(limit - used, 0),
                    reset_at=_next_month_start(today),
                    message="Monthly webhook row limit reached.",
                )
            remaining = max(limit - (used + amount), 0) if limit else None
            return ConsumeResult(
                allowed=True,
                resource=resource_key,
                tier=tier,
                used=used + amount,
                limit=limit,
                remaining=remaining,
                reset_at=_next_month_start(today),
            )

        return ConsumeResult(
            allowed=False,
            resource=resource_key,
            tier=tier,
            used=0,
            limit=0,
            remaining=0,
            reset_at=None,
            message="Unknown resource.",
        )

    def _apply_consumption(self, usage: AccountUsage, resource: str, amount: int) -> None:
        resource_key = resource.lower()
        if resource_key == "gremlin":
            usage.gremlin_messages_used = int(usage.gremlin_messages_used or 0) + amount
        elif resource_key == "byo_rows":
            usage.byo_rows_used = int(usage.byo_rows_used or 0) + amount
        elif resource_key == "list_builder":
            usage.list_builder_daily_used = int(usage.list_builder_daily_used or 0) + amount
            usage.list_builder_monthly_used = int(usage.list_builder_monthly_used or 0) + amount
        elif resource_key == "rows":
            usage.rows_used = int(usage.rows_used or 0) + amount
        elif resource_key == "webhook_rows":
            usage.webhook_rows_used = int(usage.webhook_rows_used or 0) + amount

    @classmethod
    def sync_from_stripe_sync(
        cls,
        db: Session,
        account_id: str,
        *,
        tier: str,
        addons: Optional[List[str]] = None,
        source: str = "stripe",
        monthly_row_limit: Optional[int] = None,
        expires_at: Optional[datetime] = None,
        commit: bool = True,
    ) -> None:
        entitlements = _derive_entitlements(
            tier=tier,
            addons=addons or [],
            monthly_row_limit=monthly_row_limit,
        )
        row = (
            db.query(AccountEntitlements)
            .filter(AccountEntitlements.account_id == account_id)
            .one_or_none()
        )
        if row:
            for key, value in entitlements.items():
                setattr(row, key, value)
            row.source = source
            row.synced_at = utcnow()
            row.expires_at = expires_at
            db.add(row)
        else:
            row = AccountEntitlements(
                account_id=account_id,
                source=source,
                synced_at=utcnow(),
                expires_at=expires_at,
                **entitlements,
            )
            db.add(row)
        if commit:
            db.commit()
        else:
            db.flush()


__all__ = ["UsageGateway", "ConsumeResult", "BYO_ADDON_KEY"]
