from __future__ import annotations

import hashlib
import json
from datetime import UTC, datetime, timedelta
from decimal import ROUND_HALF_UP, Decimal
from typing import Any, Literal

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

from app.models.commercial import (
    CommercialCostEvent,
    CommercialEntitlement,
    TenantSubscription,
    UsageMeterEvent,
)
from app.models.commercial_billing import CommercialBillingPeriod
from app.schemas.commercial import CommercialCostEventCreate, UsageMeterEventCreate
from app.services.commercial_access_policy import (
    CommercialConfigurationError,
    CommercialConflictError,
)
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

FOUR_PLACES = Decimal("0.0001")


class CommercialMeteringService:
    """写入追加式用量/成本事实，所有派生字段均由服务端生成。"""

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

    def record_usage(
        self,
        tenant_id: str,
        payload: UsageMeterEventCreate,
        *,
        actor_type: Literal["system", "user", "integration", "admin"],
        actor_id: str,
    ) -> tuple[UsageMeterEvent, bool]:
        tenant_id = _required(tenant_id, "租户编号")
        source_system = _required(payload.source_system, "来源系统")
        idempotency_key = _required(payload.idempotency_key, "幂等键")
        actor_id = _required(actor_id, "事件操作人")
        fingerprint = _fingerprint(tenant_id, payload.model_dump(mode="json"))
        existing = self._usage_by_idempotency(
            tenant_id,
            source_system,
            idempotency_key,
        )
        if existing is not None:
            self._assert_same_fingerprint(existing.request_fingerprint, fingerprint)
            return existing, False

        subscription = self._subscription(tenant_id, payload.subscription_id)
        entitlement = self._entitlement(
            tenant_id,
            payload.subscription_id,
            payload.entitlement_id,
        )
        # 锁定权益后再查一次，避免并发重放被后续配额校验误判为超限。
        existing = self._usage_by_idempotency(
            tenant_id,
            source_system,
            idempotency_key,
        )
        if existing is not None:
            self._assert_same_fingerprint(existing.request_fingerprint, fingerprint)
            return existing, False
        occurred_at = _utc(payload.occurred_at)
        if occurred_at > datetime.now(UTC) + timedelta(minutes=5):
            raise CommercialConfigurationError("不能写入未来发生的商业用量事实。")
        billing_period = (
            None
            if payload.event_type == "reversal"
            else CommercialBillingPeriodService(self.db).resolve(
                tenant_id,
                subscription.id,
                occurred_at,
            )
        )
        if payload.event_type == "usage" and billing_period is not None:
            self._assert_consumable(
                subscription,
                entitlement,
                billing_period,
                occurred_at,
            )

        quantity = payload.quantity
        period_key = billing_period.period_key if billing_period is not None else ""
        quota_period_key = quota_period_key_for(entitlement, subscription, occurred_at)
        billing_period_id = billing_period.id if billing_period is not None else ""
        if payload.event_type == "reversal":
            original = self._usage_event(
                tenant_id,
                payload.subscription_id,
                payload.entitlement_id,
                str(payload.reversal_of_event_id),
            )
            if original is None:
                raise LookupError("被冲回的用量事件不存在。")
            if original.event_type == "reversal":
                raise CommercialConflictError("不能再次冲回 reversal 用量事件。")
            if self.db.scalar(
                select(UsageMeterEvent.id).where(
                    UsageMeterEvent.tenant_id == tenant_id,
                    UsageMeterEvent.reversal_of_event_id == original.id,
                )
            ):
                raise CommercialConflictError("该用量事件已经冲回。")
            if quantity != -Decimal(original.quantity):
                raise CommercialConfigurationError("冲回数量必须与原事件数量完全相反。")
            period_key = original.period_key
            quota_period_key = original.quota_period_key
            billing_period_id = original.billing_period_id

        current_usage = Decimal(
            self.db.scalar(
                select(func.coalesce(func.sum(UsageMeterEvent.quantity), 0)).where(
                    UsageMeterEvent.tenant_id == tenant_id,
                    UsageMeterEvent.subscription_id == subscription.id,
                    UsageMeterEvent.entitlement_id == entitlement.id,
                    UsageMeterEvent.quota_period_key == quota_period_key,
                )
            )
            or 0
        )
        active_reservations = active_reserved_quantity(
            self.db,
            tenant_id=tenant_id,
            subscription_id=subscription.id,
            entitlement_id=entitlement.id,
            period_key=quota_period_key,
        )
        resulting_usage = current_usage + quantity
        if resulting_usage < 0:
            raise CommercialConflictError("用量抵扣或冲回后不能形成负数配额消耗。")
        if quantity > 0:
            hard_limit = (
                Decimal(entitlement.hard_limit_quantity)
                if entitlement.hard_limit_quantity is not None
                else None
            )
            if hard_limit is None and entitlement.overage_policy == "block":
                hard_limit = (
                    Decimal(entitlement.included_quantity)
                    if entitlement.included_quantity is not None
                    else None
                )
            if hard_limit is not None and resulting_usage + active_reservations > hard_limit:
                raise CommercialConflictError("用量事件将超过商业硬配额，已失败关闭。")

        row = UsageMeterEvent(
            tenant_id=tenant_id,
            subscription_id=subscription.id,
            entitlement_id=entitlement.id,
            billing_period_id=billing_period_id,
            event_type=payload.event_type,
            metric_key=entitlement.metric_key,
            quantity=quantity,
            unit=entitlement.unit,
            period_key=period_key,
            quota_period_key=quota_period_key,
            occurred_at=occurred_at,
            source_system=source_system,
            idempotency_key=idempotency_key,
            request_fingerprint=fingerprint,
            reversal_of_event_id=payload.reversal_of_event_id,
            subject_type=_blank_to_none(payload.subject_type),
            subject_id=_blank_to_none(payload.subject_id),
            actor_type=actor_type,
            actor_id=actor_id,
            correlation_id=_blank_to_none(payload.correlation_id),
            trace_id=_blank_to_none(payload.trace_id),
            metadata_json=dict(payload.metadata_json),
        )
        return self._flush_usage_idempotently(row, fingerprint)

    def record_cost(
        self,
        tenant_id: str,
        payload: CommercialCostEventCreate,
    ) -> tuple[CommercialCostEvent, bool]:
        tenant_id = _required(tenant_id, "租户编号")
        source_system = _required(payload.source_system, "来源系统")
        idempotency_key = _required(payload.idempotency_key, "幂等键")
        fingerprint = _fingerprint(tenant_id, payload.model_dump(mode="json"))
        existing = self._cost_by_idempotency(
            tenant_id,
            source_system,
            idempotency_key,
        )
        if existing is not None:
            self._assert_same_fingerprint(existing.request_fingerprint, fingerprint)
            return existing, False

        occurred_at = self._validated_cost_occurred_at(payload.occurred_at)
        subscription: TenantSubscription | None = None
        billing_period_id: str | None = None
        if payload.subscription_id:
            subscription = self._subscription(tenant_id, payload.subscription_id)
        usage_event: UsageMeterEvent | None = None
        if payload.usage_event_id:
            usage_event = self.db.scalar(
                select(UsageMeterEvent).where(
                    UsageMeterEvent.tenant_id == tenant_id,
                    UsageMeterEvent.subscription_id == payload.subscription_id,
                    UsageMeterEvent.id == payload.usage_event_id,
                )
            )
            if usage_event is None:
                raise LookupError("成本关联的用量事件不存在。")
            billing_period_id = usage_event.billing_period_id
        elif subscription is not None and payload.event_type != "reversal":
            billing_period_id = (
                CommercialBillingPeriodService(self.db)
                .resolve(
                    tenant_id,
                    subscription.id,
                    occurred_at,
                )
                .id
            )

        quantity = payload.quantity
        unit = _required(payload.unit, "成本计量单位")
        unit_cost = payload.unit_cost
        original_currency = payload.original_currency.upper()
        reporting_currency = payload.reporting_currency.upper()
        fx_rate = payload.fx_rate
        cost_category = payload.cost_category
        allocation_key = _required(payload.allocation_key, "成本分摊键")
        direction = Decimal("-1") if payload.event_type == "credit" else Decimal("1")
        cost_amount = (quantity * unit_cost * direction).quantize(
            FOUR_PLACES,
            rounding=ROUND_HALF_UP,
        )
        reporting_amount = (cost_amount * fx_rate).quantize(
            FOUR_PLACES,
            rounding=ROUND_HALF_UP,
        )

        if payload.event_type == "reversal":
            original = self._cost_event(tenant_id, str(payload.reversal_of_cost_event_id))
            if original is None:
                raise LookupError("被冲回的成本事件不存在。")
            existing = self._cost_by_idempotency(
                tenant_id,
                source_system,
                idempotency_key,
            )
            if existing is not None:
                self._assert_same_fingerprint(existing.request_fingerprint, fingerprint)
                return existing, False
            if original.event_type == "reversal":
                raise CommercialConflictError("不能再次冲回 reversal 成本事件。")
            if self.db.scalar(
                select(CommercialCostEvent.id).where(
                    CommercialCostEvent.tenant_id == tenant_id,
                    CommercialCostEvent.reversal_of_cost_event_id == original.id,
                )
            ):
                raise CommercialConflictError("该成本事件已经冲回。")
            self._assert_cost_reversal_payload(payload, original)
            subscription = (
                self._subscription(tenant_id, original.subscription_id)
                if original.subscription_id
                else None
            )
            usage_event = (
                self.db.scalar(
                    select(UsageMeterEvent).where(
                        UsageMeterEvent.tenant_id == tenant_id,
                        UsageMeterEvent.id == original.usage_event_id,
                    )
                )
                if original.usage_event_id
                else None
            )
            quantity = Decimal(original.quantity)
            unit = original.unit
            unit_cost = Decimal(original.unit_cost)
            original_currency = original.original_currency
            reporting_currency = original.reporting_currency
            fx_rate = Decimal(original.fx_rate)
            cost_category = original.cost_category
            allocation_key = original.allocation_key
            cost_amount = -Decimal(original.cost_amount)
            reporting_amount = -Decimal(original.reporting_amount)
            billing_period_id = original.billing_period_id

        if cost_amount == 0 or reporting_amount == 0:
            raise CommercialConfigurationError("零金额不能写入内部成本事实。")
        row = CommercialCostEvent(
            tenant_id=tenant_id,
            subscription_id=subscription.id if subscription else None,
            billing_period_id=billing_period_id,
            usage_event_id=usage_event.id if usage_event else None,
            event_type=payload.event_type,
            cost_category=cost_category,
            quantity=quantity,
            unit=unit,
            unit_cost=unit_cost,
            cost_amount=cost_amount,
            original_currency=original_currency,
            reporting_amount=reporting_amount,
            reporting_currency=reporting_currency,
            fx_rate=fx_rate,
            provider=_blank_to_none(payload.provider),
            sku=_blank_to_none(payload.sku),
            model_name=_blank_to_none(payload.model_name),
            allocation_key=allocation_key,
            occurred_at=occurred_at,
            source_system=source_system,
            idempotency_key=idempotency_key,
            request_fingerprint=fingerprint,
            reversal_of_cost_event_id=payload.reversal_of_cost_event_id,
            correlation_id=_blank_to_none(payload.correlation_id),
            trace_id=_blank_to_none(payload.trace_id),
            metadata_json=dict(payload.metadata_json),
        )
        return self._flush_cost_idempotently(row, fingerprint)

    def _subscription(self, tenant_id: str, subscription_id: str) -> TenantSubscription:
        row = self.db.scalar(
            select(TenantSubscription)
            .where(
                TenantSubscription.tenant_id == tenant_id,
                TenantSubscription.id == subscription_id,
            )
            .with_for_update()
        )
        if row is None:
            raise LookupError("商业订阅不存在。")
        return row

    def _entitlement(
        self,
        tenant_id: str,
        subscription_id: str,
        entitlement_id: str,
    ) -> CommercialEntitlement:
        row = self.db.scalar(
            select(CommercialEntitlement)
            .where(
                CommercialEntitlement.tenant_id == tenant_id,
                CommercialEntitlement.subscription_id == subscription_id,
                CommercialEntitlement.id == entitlement_id,
            )
            .with_for_update()
        )
        if row is None:
            raise LookupError("商业权益不存在或不属于该订阅。")
        return row

    def _usage_event(
        self,
        tenant_id: str,
        subscription_id: str,
        entitlement_id: str,
        event_id: str,
    ) -> UsageMeterEvent | None:
        return self.db.scalar(
            select(UsageMeterEvent).where(
                UsageMeterEvent.tenant_id == tenant_id,
                UsageMeterEvent.subscription_id == subscription_id,
                UsageMeterEvent.entitlement_id == entitlement_id,
                UsageMeterEvent.id == event_id,
            )
        )

    def _cost_event(self, tenant_id: str, event_id: str) -> CommercialCostEvent | None:
        return self.db.scalar(
            select(CommercialCostEvent)
            .where(
                CommercialCostEvent.tenant_id == tenant_id,
                CommercialCostEvent.id == event_id,
            )
            .with_for_update()
        )

    @staticmethod
    def _assert_consumable(
        subscription: TenantSubscription,
        entitlement: CommercialEntitlement,
        billing_period: CommercialBillingPeriod,
        occurred_at: datetime,
    ) -> None:
        if subscription.status not in {"trialing", "active"}:
            raise CommercialConflictError("订阅当前不可消费新的商业用量。")
        subscription_start = _utc(subscription.starts_at)
        subscription_end = _optional_utc(subscription.ends_at)
        period_start = _utc(billing_period.period_start)
        period_end = _utc(billing_period.period_end)
        if (
            occurred_at < subscription_start
            or (subscription_end is not None and occurred_at >= subscription_end)
            or occurred_at < period_start
            or occurred_at >= period_end
        ):
            raise CommercialConflictError("用量发生时间不在当前订阅计费周期内。")
        if entitlement.status != "active":
            raise CommercialConflictError("权益当前不可消费新的商业用量。")
        start = _utc(entitlement.effective_from)
        end = _optional_utc(entitlement.effective_to)
        if occurred_at < start or (end is not None and occurred_at >= end):
            raise CommercialConflictError("用量发生时间不在权益有效期内。")

    @staticmethod
    def _validated_cost_occurred_at(value: datetime) -> datetime:
        occurred_at = _utc(value)
        if occurred_at > datetime.now(UTC) + timedelta(minutes=5):
            raise CommercialConfigurationError("不能写入未来发生的平台成本事实。")
        return occurred_at

    @staticmethod
    def _assert_same_fingerprint(existing: str, incoming: str) -> None:
        if existing != incoming:
            raise CommercialConflictError("幂等键已被不同商业事件请求占用。")

    @staticmethod
    def _assert_cost_reversal_payload(
        payload: CommercialCostEventCreate,
        original: CommercialCostEvent,
    ) -> None:
        mismatched = (
            _blank_to_none(payload.subscription_id) != original.subscription_id
            or _blank_to_none(payload.usage_event_id) != original.usage_event_id
            or payload.cost_category != original.cost_category
            or payload.quantity != Decimal(original.quantity)
            or payload.unit.strip() != original.unit
            or payload.unit_cost != Decimal(original.unit_cost)
            or payload.original_currency.upper() != original.original_currency
            or payload.reporting_currency.upper() != original.reporting_currency
            or payload.fx_rate != Decimal(original.fx_rate)
            or _blank_to_none(payload.provider) != original.provider
            or _blank_to_none(payload.sku) != original.sku
            or _blank_to_none(payload.model_name) != original.model_name
            or payload.allocation_key.strip() != original.allocation_key
        )
        if mismatched:
            raise CommercialConfigurationError("成本冲回参数必须与原始成本事实完全一致。")

    def _usage_by_idempotency(
        self,
        tenant_id: str,
        source_system: str,
        idempotency_key: str,
    ) -> UsageMeterEvent | None:
        return self.db.scalar(
            select(UsageMeterEvent).where(
                UsageMeterEvent.tenant_id == tenant_id,
                UsageMeterEvent.source_system == source_system.strip(),
                UsageMeterEvent.idempotency_key == idempotency_key.strip(),
            )
        )

    def _cost_by_idempotency(
        self,
        tenant_id: str,
        source_system: str,
        idempotency_key: str,
    ) -> CommercialCostEvent | None:
        return self.db.scalar(
            select(CommercialCostEvent).where(
                CommercialCostEvent.tenant_id == tenant_id,
                CommercialCostEvent.source_system == source_system.strip(),
                CommercialCostEvent.idempotency_key == idempotency_key.strip(),
            )
        )

    def _flush_usage_idempotently(
        self,
        row: UsageMeterEvent,
        fingerprint: str,
    ) -> tuple[UsageMeterEvent, bool]:
        try:
            with self.db.begin_nested():
                self.db.add(row)
                self.db.flush()
            return row, True
        except IntegrityError:
            existing = self._usage_by_idempotency(
                row.tenant_id,
                row.source_system,
                row.idempotency_key,
            )
            if existing is None:
                raise
            self._assert_same_fingerprint(existing.request_fingerprint, fingerprint)
            return existing, False

    def _flush_cost_idempotently(
        self,
        row: CommercialCostEvent,
        fingerprint: str,
    ) -> tuple[CommercialCostEvent, bool]:
        try:
            with self.db.begin_nested():
                self.db.add(row)
                self.db.flush()
            return row, True
        except IntegrityError:
            existing = self._cost_by_idempotency(
                row.tenant_id,
                row.source_system,
                row.idempotency_key,
            )
            if existing is None:
                raise
            self._assert_same_fingerprint(existing.request_fingerprint, fingerprint)
            return existing, False


def _fingerprint(tenant_id: str, payload: dict[str, Any]) -> str:
    canonical = json.dumps(
        {"tenant_id": tenant_id, "payload": payload},
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        default=str,
    )
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


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


def _optional_utc(value: datetime | None) -> datetime | None:
    return _utc(value) if value is not None else None


def _blank_to_none(value: str | None) -> str | None:
    normalized = str(value or "").strip()
    return normalized or None


def _required(value: str | None, field_name: str) -> str:
    normalized = str(value or "").strip()
    if not normalized:
        raise CommercialConfigurationError(f"{field_name}不能为空。")
    return normalized
