from __future__ import annotations

from datetime import UTC, datetime

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

from app.models.commercial import (
    CommercialEntitlement,
    TenantCommercialPlan,
    TenantSubscription,
    UsageMeterEvent,
)
from app.schemas.commercial import (
    CommercialEntitlementUpsert,
    CommercialPlanActivationRead,
    CommercialPlanCreate,
    CommercialSubscriptionCreate,
    CommercialSubscriptionTransition,
)
from app.services.commercial_access_policy import (
    CommercialConfigurationError,
    CommercialConflictError,
)
from app.services.commercial_admin_audit import (
    CommercialAdminAuditService,
    service_request_id,
    snapshot_resource,
)
from app.services.commercial_billing_periods import CommercialBillingPeriodService

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


class CommercialAdminService:
    """平台侧维护有版本的套餐、订阅快照和权益配置。"""

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

    def create_plan(
        self,
        tenant_id: str,
        payload: CommercialPlanCreate,
        *,
        actor_id: str,
        request_id: str | None = None,
        reason: str | None = None,
    ) -> TenantCommercialPlan:
        tenant_id = _required(tenant_id, "租户编号")
        plan_code = _required(payload.plan_code, "套餐编码")
        actor_id = _required(actor_id, "配置操作人")
        version = (
            int(
                self.db.scalar(
                    select(func.max(TenantCommercialPlan.version)).where(
                        TenantCommercialPlan.tenant_id == tenant_id,
                        TenantCommercialPlan.plan_code == plan_code,
                    )
                )
                or 0
            )
            + 1
        )
        row = TenantCommercialPlan(
            tenant_id=tenant_id,
            plan_code=plan_code,
            name=_required(payload.name, "套餐名称"),
            pricing_model=payload.pricing_model,
            billing_interval=payload.billing_interval,
            currency=payload.currency.upper(),
            base_fee=payload.base_fee,
            included_seats=payload.included_seats,
            overage_enabled=payload.overage_enabled,
            status="draft",
            effective_from=_utc(payload.effective_from),
            effective_to=_optional_utc(payload.effective_to),
            version=version,
            contract_terms_json=dict(payload.contract_terms_json),
            created_by=actor_id,
        )
        self.db.add(row)
        self.db.flush()
        request = request_id or service_request_id("plan-create")
        CommercialAdminAuditService(self.db).record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor_id,
            request_id=request,
            reason=reason or payload.reason,
            action="plan_created",
            resource=row,
            before={},
            after=snapshot_resource(row),
        )
        return row

    def activate_plan(
        self,
        tenant_id: str,
        plan_id: str,
        *,
        expected_version: int,
        actor_id: str = "platform-admin",
        request_id: str | None = None,
        reason: str = "平台管理员激活套餐版本",
    ) -> CommercialPlanActivationRead:
        plan = self._plan(tenant_id, plan_id, for_update=True)
        if plan is None:
            raise LookupError("商业套餐不存在。")
        if plan.version != expected_version:
            raise CommercialConflictError(f"商业套餐版本冲突，当前版本为 {plan.version}。")
        if plan.status == "retired":
            raise CommercialConflictError("已退役套餐不能重新激活，请创建新版本。")

        request = request_id or service_request_id("plan-activate")
        actor = _required(actor_id, "配置操作人")
        audit = CommercialAdminAuditService(self.db)
        before_plan = snapshot_resource(plan)
        retired_ids: list[str] = []
        current_rows = list(
            self.db.scalars(
                select(TenantCommercialPlan)
                .where(
                    TenantCommercialPlan.tenant_id == tenant_id,
                    TenantCommercialPlan.plan_code == plan.plan_code,
                    TenantCommercialPlan.status == "active",
                    TenantCommercialPlan.id != plan.id,
                )
                .with_for_update()
            ).all()
        )
        for current in current_rows:
            before_current = snapshot_resource(current)
            if _utc(plan.effective_from) <= _utc(current.effective_from):
                raise CommercialConfigurationError("新套餐版本的生效时间必须晚于当前激活版本。")
            current.status = "retired"
            current.effective_to = plan.effective_from
            retired_ids.append(current.id)
            audit.record(
                tenant_id=tenant_id,
                actor_type="user",
                actor_id=actor,
                request_id=request,
                reason=reason,
                action="plan_retired",
                resource=current,
                before=before_current,
                after=snapshot_resource(current),
            )
        plan.status = "active"
        self.db.flush()
        audit.record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor,
            request_id=request,
            reason=reason,
            action="plan_activated",
            resource=plan,
            before=before_plan,
            after=snapshot_resource(plan),
        )
        return CommercialPlanActivationRead(
            plan=plan,
            retired_plan_ids=retired_ids,
        )

    def create_subscription(
        self,
        tenant_id: str,
        payload: CommercialSubscriptionCreate,
        *,
        actor_id: str,
        request_id: str | None = None,
        reason: str | None = None,
    ) -> TenantSubscription:
        tenant_id = _required(tenant_id, "租户编号")
        actor_id = _required(actor_id, "配置操作人")
        plan = self._plan(tenant_id, payload.plan_id, for_update=True)
        if plan is None:
            raise LookupError("订阅引用的商业套餐不存在。")
        if plan.status != "active":
            raise CommercialConfigurationError("只能基于已激活的套餐创建订阅。")
        starts_at = _utc(payload.starts_at)
        ends_at = _optional_utc(payload.ends_at)
        period_start = _utc(payload.current_period_start)
        period_end = _utc(payload.current_period_end)
        if starts_at < _utc(plan.effective_from) or (
            plan.effective_to is not None and starts_at >= _utc(plan.effective_to)
        ):
            raise CommercialConfigurationError("订阅开始时间不在套餐有效期内。")
        if period_start < starts_at or (ends_at is not None and period_end > ends_at):
            raise CommercialConfigurationError("当前计费周期必须位于订阅合同有效期内。")
        current = self._current_subscription(tenant_id, for_update=True)
        if current is not None:
            raise CommercialConflictError(
                f"租户已有当前订阅 {current.subscription_key}，请先结束原订阅。"
            )
        row = TenantSubscription(
            tenant_id=tenant_id,
            subscription_key=_required(payload.subscription_key, "订阅键"),
            plan_id=plan.id,
            status=payload.status,
            starts_at=starts_at,
            ends_at=ends_at,
            current_period_start=period_start,
            current_period_end=period_end,
            seats=payload.seats,
            base_fee_snapshot=plan.base_fee,
            currency=plan.currency,
            billing_interval=plan.billing_interval,
            auto_renew=payload.auto_renew,
            external_provider=_blank_to_none(payload.external_provider),
            external_subscription_id=_blank_to_none(payload.external_subscription_id),
            version=1,
            metadata_json=dict(payload.metadata_json),
            created_by=actor_id,
        )
        self.db.add(row)
        self.db.flush()
        request = request_id or service_request_id("subscription-create")
        audit_reason = reason or payload.reason
        audit = CommercialAdminAuditService(self.db)
        audit.record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor_id,
            request_id=request,
            reason=audit_reason,
            action="subscription_created",
            resource=row,
            before={},
            after=snapshot_resource(row),
        )
        period, _ = CommercialBillingPeriodService(self.db).create_initial(
            row,
            plan,
            actor_id=actor_id,
            request_id=request,
        )
        audit.record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor_id,
            request_id=request,
            reason=audit_reason,
            action="billing_period_created",
            resource=period,
            before={},
            after=snapshot_resource(period),
        )
        return row

    def activate_subscription(
        self,
        tenant_id: str,
        subscription_id: str,
        *,
        expected_version: int,
        actor_id: str = "platform-admin",
        request_id: str | None = None,
        reason: str = "平台管理员重新激活订阅",
    ) -> TenantSubscription:
        row = self._subscription(tenant_id, subscription_id, for_update=True)
        if row is None:
            raise LookupError("商业订阅不存在。")
        if row.version != expected_version:
            raise CommercialConflictError(f"订阅版本冲突，当前版本为 {row.version}。")
        if row.status in {"canceled", "expired"}:
            raise CommercialConflictError("已取消或过期订阅不能重新激活。")
        current = self._current_subscription(tenant_id, for_update=True)
        if current is not None and current.id != row.id:
            raise CommercialConflictError("租户已有另一个当前订阅，不能激活该订阅。")
        before = snapshot_resource(row)
        row.status = "active"
        row.version += 1
        self.db.flush()
        CommercialAdminAuditService(self.db).record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor_id,
            request_id=request_id or service_request_id("subscription-activate"),
            reason=reason,
            action="subscription_activated",
            resource=row,
            before=before,
            after=snapshot_resource(row),
        )
        return row

    def transition_subscription(
        self,
        tenant_id: str,
        subscription_id: str,
        payload: CommercialSubscriptionTransition,
        *,
        actor_id: str,
        request_id: str | None = None,
    ) -> TenantSubscription:
        """暂停或终止订阅；终态只能通过新订阅恢复商业服务。"""

        row = self._subscription(tenant_id, subscription_id, for_update=True)
        if row is None:
            raise LookupError("商业订阅不存在。")
        if row.version != payload.expected_version:
            raise CommercialConflictError(f"订阅版本冲突，当前版本为 {row.version}。")
        target = payload.target_status
        if row.status == target:
            return row
        allowed = {
            "trialing": {"past_due", "suspended", "canceled", "expired"},
            "active": {"past_due", "suspended", "canceled", "expired"},
            "past_due": {"suspended", "canceled", "expired"},
            "suspended": {"canceled", "expired"},
            "canceled": set(),
            "expired": set(),
        }
        if target not in allowed.get(str(row.status), set()):
            raise CommercialConflictError(f"订阅不能从 {row.status} 转换为 {target}。")

        before = snapshot_resource(row)
        changed_at = datetime.now(UTC)
        metadata = dict(row.metadata_json or {})
        history = list(
            metadata.get("status_history")
            if isinstance(metadata.get("status_history"), list)
            else []
        )
        history.append(
            {
                "from": row.status,
                "to": target,
                "reason": _required(payload.reason, "状态变更原因"),
                "actor_id": _required(actor_id, "配置操作人"),
                "changed_at": changed_at.isoformat(),
            }
        )
        metadata["status_history"] = history[-50:]
        row.metadata_json = metadata
        row.status = target
        if target == "canceled":
            row.canceled_at = changed_at
        if target in {"canceled", "expired"} and (
            row.ends_at is None or _utc(row.ends_at) > changed_at
        ):
            row.ends_at = changed_at
        row.version += 1
        self.db.flush()
        CommercialAdminAuditService(self.db).record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor_id,
            request_id=request_id or service_request_id("subscription-transition"),
            reason=payload.reason,
            action="subscription_transitioned",
            resource=row,
            before=before,
            after=snapshot_resource(row),
        )
        return row

    def upsert_entitlement(
        self,
        tenant_id: str,
        payload: CommercialEntitlementUpsert,
        *,
        actor_id: str = "platform-admin",
        request_id: str | None = None,
        reason: str | None = None,
    ) -> CommercialEntitlement:
        tenant_id = _required(tenant_id, "租户编号")
        entitlement_key = _required(payload.entitlement_key, "权益键")
        subscription = self._subscription(
            tenant_id,
            payload.subscription_id,
            for_update=True,
        )
        if subscription is None:
            raise LookupError("权益引用的商业订阅不存在。")
        effective_from = _utc(payload.effective_from)
        effective_to = _optional_utc(payload.effective_to)
        if effective_from < _utc(subscription.starts_at) or (
            subscription.ends_at is not None
            and (effective_to is None or effective_to > _utc(subscription.ends_at))
        ):
            raise CommercialConfigurationError("权益有效期必须位于订阅合同有效期内。")
        row = self.db.scalar(
            select(CommercialEntitlement)
            .where(
                CommercialEntitlement.tenant_id == tenant_id,
                CommercialEntitlement.subscription_id == subscription.id,
                CommercialEntitlement.entitlement_key == entitlement_key,
            )
            .with_for_update()
        )
        values = {
            "metric_key": _required(payload.metric_key, "计量键"),
            "entitlement_type": payload.entitlement_type,
            "unit": _required(payload.unit, "计量单位"),
            "included_quantity": payload.included_quantity,
            "hard_limit_quantity": payload.hard_limit_quantity,
            "reset_interval": payload.reset_interval,
            "overage_policy": payload.overage_policy,
            "status": payload.status,
            "effective_from": effective_from,
            "effective_to": effective_to,
            "config_json": dict(payload.config_json),
        }
        before = snapshot_resource(row) if row is not None else {}
        created = row is None
        if row is None:
            row = CommercialEntitlement(
                tenant_id=tenant_id,
                subscription_id=subscription.id,
                entitlement_key=entitlement_key,
                version=1,
                **values,
            )
            self.db.add(row)
        else:
            if all(getattr(row, key) == value for key, value in values.items()):
                return row
            has_usage = self.db.scalar(
                select(UsageMeterEvent.id)
                .where(
                    UsageMeterEvent.tenant_id == tenant_id,
                    UsageMeterEvent.entitlement_id == row.id,
                )
                .limit(1)
            )
            immutable_after_usage = {key: value for key, value in values.items() if key != "status"}
            if has_usage and any(
                getattr(row, key) != value for key, value in immutable_after_usage.items()
            ):
                raise CommercialConflictError(
                    "已有用量事实的权益不能回改配额、计价或有效期，"
                    "请创建新订阅版本；仅允许暂停或恢复当前权益。"
                )
            for key, value in values.items():
                setattr(row, key, value)
            row.version += 1
        self.db.flush()
        CommercialAdminAuditService(self.db).record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor_id,
            request_id=request_id or service_request_id("entitlement-upsert"),
            reason=reason or payload.reason,
            action="entitlement_created" if created else "entitlement_updated",
            resource=row,
            before=before,
            after=snapshot_resource(row),
        )
        return row

    def activate_entitlement(
        self,
        tenant_id: str,
        entitlement_id: str,
        *,
        expected_version: int,
        actor_id: str = "platform-admin",
        request_id: str | None = None,
        reason: str = "平台管理员激活商业权益",
    ) -> CommercialEntitlement:
        row = self.db.scalar(
            select(CommercialEntitlement)
            .where(
                CommercialEntitlement.tenant_id == tenant_id,
                CommercialEntitlement.id == entitlement_id,
            )
            .with_for_update()
        )
        if row is None:
            raise LookupError("商业权益不存在。")
        if row.version != expected_version:
            raise CommercialConflictError(f"权益版本冲突，当前版本为 {row.version}。")
        if row.status == "expired":
            raise CommercialConflictError("已过期权益不能重新激活，请创建新订阅版本。")
        before = snapshot_resource(row)
        row.status = "active"
        row.version += 1
        self.db.flush()
        CommercialAdminAuditService(self.db).record(
            tenant_id=tenant_id,
            actor_type="user",
            actor_id=actor_id,
            request_id=request_id or service_request_id("entitlement-activate"),
            reason=reason,
            action="entitlement_activated",
            resource=row,
            before=before,
            after=snapshot_resource(row),
        )
        return row

    def _plan(
        self,
        tenant_id: str,
        plan_id: str,
        *,
        for_update: bool,
    ) -> TenantCommercialPlan | None:
        statement = select(TenantCommercialPlan).where(
            TenantCommercialPlan.tenant_id == tenant_id,
            TenantCommercialPlan.id == plan_id,
        )
        if for_update:
            statement = statement.with_for_update()
        return self.db.scalar(statement)

    def _subscription(
        self,
        tenant_id: str,
        subscription_id: str,
        *,
        for_update: bool,
    ) -> TenantSubscription | None:
        statement = select(TenantSubscription).where(
            TenantSubscription.tenant_id == tenant_id,
            TenantSubscription.id == subscription_id,
        )
        if for_update:
            statement = statement.with_for_update()
        return self.db.scalar(statement)

    def _current_subscription(
        self,
        tenant_id: str,
        *,
        for_update: bool,
    ) -> TenantSubscription | None:
        statement = select(TenantSubscription).where(
            TenantSubscription.tenant_id == tenant_id,
            TenantSubscription.status.in_(CURRENT_SUBSCRIPTION_STATUSES),
        )
        if for_update:
            statement = statement.with_for_update()
        return self.db.scalar(statement)


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
