"""不可变商业账期的签发、定位与历史读取。"""

from __future__ import annotations

from datetime import UTC, datetime

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

from app.models.commercial import TenantCommercialPlan, TenantSubscription
from app.models.commercial_billing import CommercialBillingPeriod
from app.schemas.commercial_billing import CommercialBillingPeriodRead
from app.services.commercial_access_policy import (
    CommercialConfigurationError,
    CommercialConflictError,
)
from app.services.commercial_periods import billing_period_key


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

    def create_initial(
        self,
        subscription: TenantSubscription,
        plan: TenantCommercialPlan,
        *,
        actor_id: str,
        request_id: str,
    ) -> tuple[CommercialBillingPeriod, bool]:
        return self.issue(
            subscription,
            plan,
            period_start=subscription.current_period_start,
            period_end=subscription.current_period_end,
            source="subscription_created",
            actor_id=actor_id,
            idempotency_key=f"{request_id}:initial-period",
        )

    def issue(
        self,
        subscription: TenantSubscription,
        plan: TenantCommercialPlan,
        *,
        period_start: datetime,
        period_end: datetime,
        source: str,
        actor_id: str,
        idempotency_key: str,
    ) -> tuple[CommercialBillingPeriod, bool]:
        self._validate_scope(subscription, plan)
        start = _utc(period_start)
        end = _utc(period_end)
        if end <= start:
            raise CommercialConfigurationError("账期结束时间必须晚于开始时间。")
        if start < _utc(subscription.starts_at):
            raise CommercialConfigurationError("账期不能早于订阅合同开始时间。")
        if subscription.ends_at is not None and end > _utc(subscription.ends_at):
            raise CommercialConfigurationError("账期不能超过订阅合同结束时间。")
        request_key = _required(idempotency_key, "账期幂等键")[:160]
        existing = self._by_idempotency(subscription, request_key)
        expected_key = billing_period_key(
            subscription.tenant_id,
            subscription.id,
            start,
            end,
        )
        if existing is not None:
            self._assert_same_window(existing, start, end, expected_key)
            return existing, False
        overlap = self.db.scalar(
            select(CommercialBillingPeriod.id).where(
                CommercialBillingPeriod.tenant_id == subscription.tenant_id,
                CommercialBillingPeriod.subscription_id == subscription.id,
                CommercialBillingPeriod.period_start < end,
                CommercialBillingPeriod.period_end > start,
            )
        )
        if overlap is not None:
            raise CommercialConflictError("新账期与已签发不可变账期重叠。")
        sequence = (
            int(
                self.db.scalar(
                    select(func.max(CommercialBillingPeriod.period_sequence)).where(
                        CommercialBillingPeriod.tenant_id == subscription.tenant_id,
                        CommercialBillingPeriod.subscription_id == subscription.id,
                    )
                )
                or 0
            )
            + 1
        )
        row = CommercialBillingPeriod(
            tenant_id=subscription.tenant_id,
            subscription_id=subscription.id,
            plan_id=plan.id,
            period_sequence=sequence,
            period_key=expected_key,
            status="issued",
            period_start=start,
            period_end=end,
            subscription_status_snapshot=subscription.status,
            plan_code_snapshot=plan.plan_code,
            plan_version_snapshot=plan.version,
            pricing_model_snapshot=plan.pricing_model,
            billing_interval=subscription.billing_interval,
            currency=subscription.currency,
            base_fee_snapshot=subscription.base_fee_snapshot,
            seats_snapshot=subscription.seats,
            source=source,
            idempotency_key=request_key,
            created_by=_required(actor_id, "账期签发人")[:120],
        )
        try:
            with self.db.begin_nested():
                self.db.add(row)
                self.db.flush()
            return row, True
        except IntegrityError:
            existing = self._by_idempotency(subscription, request_key)
            if existing is None:
                raise
            self._assert_same_window(existing, start, end, expected_key)
            return existing, False

    def resolve(
        self,
        tenant_id: str,
        subscription_id: str,
        occurred_at: datetime,
        *,
        for_update: bool = False,
    ) -> CommercialBillingPeriod:
        when = _utc(occurred_at)
        statement = select(CommercialBillingPeriod).where(
            CommercialBillingPeriod.tenant_id == _required(tenant_id, "租户编号"),
            CommercialBillingPeriod.subscription_id == _required(subscription_id, "订阅编号"),
            CommercialBillingPeriod.period_start <= when,
            CommercialBillingPeriod.period_end > when,
        )
        if for_update:
            statement = statement.with_for_update()
        rows = list(self.db.scalars(statement.limit(2)).all())
        if not rows:
            raise CommercialConflictError("发生时间没有对应的已签发不可变账期。")
        if len(rows) != 1:
            raise CommercialConflictError("订阅存在重叠账期，已失败关闭。")
        return rows[0]

    def list_periods(
        self,
        tenant_id: str,
        *,
        subscription_id: str | None = None,
        limit: int = 200,
        offset: int = 0,
    ) -> list[CommercialBillingPeriod]:
        statement = select(CommercialBillingPeriod).where(
            CommercialBillingPeriod.tenant_id == _required(tenant_id, "租户编号")
        )
        if subscription_id:
            statement = statement.where(
                CommercialBillingPeriod.subscription_id == subscription_id.strip()
            )
        return list(
            self.db.scalars(
                statement.order_by(
                    CommercialBillingPeriod.period_start.desc(),
                    CommercialBillingPeriod.id.desc(),
                )
                .offset(offset)
                .limit(limit)
            ).all()
        )

    @staticmethod
    def to_read(
        row: CommercialBillingPeriod,
        *,
        as_of: datetime | None = None,
    ) -> CommercialBillingPeriodRead:
        when = _utc(as_of or datetime.now(UTC))
        start = _utc(row.period_start)
        end = _utc(row.period_end)
        temporal_state = "upcoming" if when < start else "elapsed" if when >= end else "current"
        payload = {
            column.name: getattr(row, column.name)
            for column in CommercialBillingPeriod.__table__.columns
        }
        payload["temporal_state"] = temporal_state
        return CommercialBillingPeriodRead.model_validate(payload)

    def _by_idempotency(
        self,
        subscription: TenantSubscription,
        idempotency_key: str,
    ) -> CommercialBillingPeriod | None:
        return self.db.scalar(
            select(CommercialBillingPeriod).where(
                CommercialBillingPeriod.tenant_id == subscription.tenant_id,
                CommercialBillingPeriod.subscription_id == subscription.id,
                CommercialBillingPeriod.idempotency_key == idempotency_key,
            )
        )

    @staticmethod
    def _validate_scope(
        subscription: TenantSubscription,
        plan: TenantCommercialPlan,
    ) -> None:
        if subscription.tenant_id != plan.tenant_id or subscription.plan_id != plan.id:
            raise CommercialConfigurationError("账期套餐与订阅租户范围不一致。")

    @staticmethod
    def _assert_same_window(
        row: CommercialBillingPeriod,
        start: datetime,
        end: datetime,
        period_key: str,
    ) -> None:
        if (
            _utc(row.period_start) != start
            or _utc(row.period_end) != end
            or row.period_key != period_key
        ):
            raise CommercialConflictError("账期幂等键已被不同周期占用。")


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


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