from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal
from typing import Literal

from sqlalchemy import func, select
from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.models.commercial import (
    CommercialEntitlement,
    TenantCommercialPlan,
    TenantSubscription,
    UsageMeterEvent,
)
from app.models.commercial_billing import CommercialBillingPeriod
from app.schemas.commercial import (
    CommercialAccountRead,
    CommercialQuotaRead,
    EntitlementGateRead,
)
from app.services.commercial_access_policy import CommercialAccessPolicy
from app.services.commercial_billing_periods import CommercialBillingPeriodService
from app.services.commercial_periods import quota_period_key_for
from app.services.commercial_runtime_reservations import active_reserved_quantity

CURRENT_SUBSCRIPTION_STATUSES = {"trialing", "active", "past_due", "suspended"}


class CommercialEntitlementService:
    """计算租户可见配额，并把商业门禁与安全门禁做收紧式合并。"""

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

    def get_account(
        self,
        current_user: CurrentUserContext,
        *,
        as_of: datetime | None = None,
    ) -> CommercialAccountRead:
        tenant_id = CommercialAccessPolicy.require_account_read(current_user)
        return self.get_account_for_tenant(tenant_id, as_of=as_of)

    def get_account_for_tenant(
        self,
        tenant_id: str,
        *,
        as_of: datetime | None = None,
    ) -> CommercialAccountRead:
        normalized_as_of = _utc(as_of or datetime.now(UTC))
        subscription = self.db.scalar(
            select(TenantSubscription).where(
                TenantSubscription.tenant_id == tenant_id,
                TenantSubscription.status.in_(CURRENT_SUBSCRIPTION_STATUSES),
            )
        )
        if subscription is None:
            return CommercialAccountRead(
                tenant_id=tenant_id,
                as_of=normalized_as_of,
                data_status="unavailable",
                notes=["租户尚未配置当前商业订阅。"],
            )
        plan = self.db.scalar(
            select(TenantCommercialPlan).where(
                TenantCommercialPlan.tenant_id == tenant_id,
                TenantCommercialPlan.id == subscription.plan_id,
            )
        )
        if plan is None:
            return CommercialAccountRead(
                tenant_id=tenant_id,
                as_of=normalized_as_of,
                data_status="partial",
                subscription=subscription,
                notes=["订阅套餐快照引用缺失，商业账户数据不完整。"],
            )
        entitlements = list(
            self.db.scalars(
                select(CommercialEntitlement)
                .where(
                    CommercialEntitlement.tenant_id == tenant_id,
                    CommercialEntitlement.subscription_id == subscription.id,
                )
                .order_by(CommercialEntitlement.entitlement_key)
            ).all()
        )
        quotas = [
            self._quota(subscription, entitlement, normalized_as_of) for entitlement in entitlements
        ]
        notes: list[str] = []
        data_status: Literal["available", "partial", "unavailable"] = "available"
        if not quotas:
            data_status = "partial"
            notes.append("当前订阅尚未配置任何商业权益。")
        if subscription.status not in {"trialing", "active"}:
            data_status = "partial"
            notes.append("当前订阅不是可消费状态，所有商业权益将失败关闭。")
        return CommercialAccountRead(
            tenant_id=tenant_id,
            as_of=normalized_as_of,
            data_status=data_status,
            plan=plan,
            subscription=subscription,
            quotas=quotas,
            notes=notes,
        )

    def check(
        self,
        tenant_id: str,
        *,
        entitlement_key: str,
        requested_quantity: Decimal,
        security_decision: Literal["allow", "deny", "human_review"],
        as_of: datetime | None = None,
    ) -> EntitlementGateRead:
        """最终允许必须同时满足商业门禁和上游安全门禁；商业配置不能放宽安全。"""
        if requested_quantity <= 0:
            raise ValueError("权益检查数量必须大于 0。")
        account = self.get_account_for_tenant(tenant_id, as_of=as_of)
        quota = next(
            (
                item
                for item in account.quotas
                if item.entitlement.entitlement_key == entitlement_key
            ),
            None,
        )
        commercial_allowed = bool(quota and self._allows(quota, requested_quantity))
        final_allowed = commercial_allowed and security_decision == "allow"
        if security_decision == "deny":
            reason = "安全或租户门禁拒绝；商业权益不能覆盖该拒绝。"
        elif security_decision == "human_review":
            reason = "高风险动作必须人工审核；商业权益不能改为自动执行。"
        elif quota is None:
            reason = "未配置对应商业权益。"
        elif not commercial_allowed:
            reason = quota.reason
        else:
            reason = "商业权益和安全门禁均允许。"
        return EntitlementGateRead(
            tenant_id=tenant_id,
            entitlement_key=entitlement_key,
            requested_quantity=requested_quantity,
            commercial_allowed=commercial_allowed,
            security_decision=security_decision,
            final_allowed=final_allowed,
            reason=reason,
            quota=quota,
        )

    def _quota(
        self,
        subscription: TenantSubscription,
        entitlement: CommercialEntitlement,
        as_of: datetime,
    ) -> CommercialQuotaRead:
        billing_period = CommercialBillingPeriodService(self.db).resolve(
            subscription.tenant_id,
            subscription.id,
            as_of,
        )
        quota_period_key = quota_period_key_for(entitlement, subscription, as_of)
        raw_used = Decimal(
            self.db.scalar(
                select(func.coalesce(func.sum(UsageMeterEvent.quantity), 0)).where(
                    UsageMeterEvent.tenant_id == subscription.tenant_id,
                    UsageMeterEvent.subscription_id == subscription.id,
                    UsageMeterEvent.entitlement_id == entitlement.id,
                    UsageMeterEvent.quota_period_key == quota_period_key,
                    UsageMeterEvent.occurred_at <= as_of,
                )
            )
            or 0
        )
        used = max(Decimal("0"), raw_used)
        reserved = max(
            Decimal("0"),
            active_reserved_quantity(
                self.db,
                tenant_id=subscription.tenant_id,
                subscription_id=subscription.id,
                entitlement_id=entitlement.id,
                period_key=quota_period_key,
            ),
        )
        included = (
            Decimal(entitlement.included_quantity)
            if entitlement.included_quantity is not None
            else None
        )
        hard_limit = (
            Decimal(entitlement.hard_limit_quantity)
            if entitlement.hard_limit_quantity is not None
            else None
        )
        committed_and_reserved = used + reserved
        included_remaining = (
            max(Decimal("0"), included - committed_and_reserved) if included is not None else None
        )
        hard_remaining = (
            max(Decimal("0"), hard_limit - committed_and_reserved)
            if hard_limit is not None
            else None
        )
        overage = max(Decimal("0"), used - included) if included is not None else Decimal("0")
        active = self._active(subscription, entitlement, billing_period, as_of)
        if not active:
            status = "inactive"
            allowed = False
            reason = "订阅或权益当前不可用。"
        elif entitlement.entitlement_type == "unlimited":
            status = "unlimited"
            allowed = True
            reason = "权益为无限量。"
        else:
            effective_limit = hard_limit
            if effective_limit is None and entitlement.overage_policy == "block":
                effective_limit = included
            exhausted = effective_limit is not None and committed_and_reserved >= effective_limit
            if exhausted:
                status = "exhausted"
                allowed = False
                reason = "商业硬配额已耗尽。"
            else:
                approaching = bool(
                    effective_limit is not None
                    and effective_limit > 0
                    and committed_and_reserved / effective_limit >= Decimal("0.8")
                )
                status = "approaching" if approaching else "available"
                allowed = True
                reason = "商业权益可用。"
        return CommercialQuotaRead(
            entitlement=entitlement,
            billing_period_id=billing_period.id,
            period_key=billing_period.period_key,
            quota_period_key=quota_period_key,
            used_quantity=used,
            reserved_quantity=reserved,
            included_remaining=included_remaining,
            hard_limit_remaining=hard_remaining,
            overage_quantity=overage,
            status=status,
            commercially_allowed=allowed,
            reason=reason,
        )

    @staticmethod
    def _allows(quota: CommercialQuotaRead, requested_quantity: Decimal) -> bool:
        if not quota.commercially_allowed:
            return False
        if quota.status == "unlimited":
            return True
        entitlement = quota.entitlement
        limit = entitlement.hard_limit_quantity
        if limit is None and entitlement.overage_policy == "block":
            limit = entitlement.included_quantity
        return (
            limit is None
            or quota.used_quantity + quota.reserved_quantity + requested_quantity <= limit
        )

    @staticmethod
    def _active(
        subscription: TenantSubscription,
        entitlement: CommercialEntitlement,
        billing_period: CommercialBillingPeriod,
        as_of: datetime,
    ) -> bool:
        if subscription.status not in {"trialing", "active"} or entitlement.status != "active":
            return False
        subscription_start = _utc(subscription.starts_at)
        subscription_end = _utc(subscription.ends_at) if subscription.ends_at else None
        period_start = _utc(billing_period.period_start)
        period_end = _utc(billing_period.period_end)
        if (
            as_of < subscription_start
            or (subscription_end is not None and as_of >= subscription_end)
            or as_of < period_start
            or as_of >= period_end
        ):
            return False
        effective_from = _utc(entitlement.effective_from)
        effective_to = _utc(entitlement.effective_to) if entitlement.effective_to else None
        return as_of >= effective_from and (effective_to is None or as_of < effective_to)


def _utc(value: datetime) -> datetime:
    return value.replace(tzinfo=UTC) if value.tzinfo is None else value.astimezone(UTC)
