"""安全补偿过期的商业运行时额度预占。"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, datetime

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.agent_run import AgentRun, AgentToolCall
from app.models.commercial_runtime import CommercialRuntimeReservation
from app.services.commercial_runtime_bridge import CommercialRuntimeBridge
from app.services.commercial_runtime_metering import RuntimeMeteringResult
from app.services.commercial_runtime_reservations import (
    CommercialRuntimeReservationService,
)

TERMINAL_RUN_STATUSES = {"succeeded", "failed", "blocked", "cancelled", "canceled"}


@dataclass(frozen=True)
class RuntimeReservationReconciliationBatch:
    items: tuple[RuntimeMeteringResult, ...]
    settled: int
    released: int
    deferred: int
    errors: int


class CommercialRuntimeReconciler:
    """只在能证明工具终态时结算或释放；不按超时猜测业务结果。"""

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

    def reconcile_expired(
        self,
        *,
        as_of: datetime | None = None,
        limit: int = 100,
    ) -> RuntimeReservationReconciliationBatch:
        when = _utc(as_of or datetime.now(UTC))
        candidates = CommercialRuntimeReservationService(self.db).expired_candidates(
            as_of=when,
            limit=limit,
        )
        candidate_ids = [row.id for row in candidates]
        self.db.rollback()
        items = tuple(self._reconcile_one(row_id, when) for row_id in candidate_ids)
        return RuntimeReservationReconciliationBatch(
            items=items,
            settled=sum(item.status in {"created", "replayed"} for item in items),
            released=sum(
                item.reason_code
                in {"expired_without_business_call", "tool_call_not_billable"}
                for item in items
            ),
            deferred=sum(item.collecting for item in items),
            errors=sum(item.status == "error" for item in items),
        )

    def _reconcile_one(
        self,
        reservation_id: str,
        when: datetime,
    ) -> RuntimeMeteringResult:
        tool_call_id = ""
        run_id = ""
        tenant_id = None
        try:
            reservation = CommercialRuntimeReservationService(self.db).get_for_update(
                reservation_id
            )
            tool_call_id = reservation.tool_call_id
            run_id = reservation.run_id
            tenant_id = reservation.tenant_id
            if reservation.status != "reserved" or _utc(reservation.expires_at) > when:
                self.db.rollback()
                return _result(
                    reservation,
                    reason_code="reservation_no_longer_expired",
                    reason="预占已由其他补偿器处理或尚未过期。",
                )
            tool_call = self.db.scalar(
                select(AgentToolCall).where(AgentToolCall.id == reservation.tool_call_id)
            )
            if tool_call is not None:
                self.db.rollback()
                return CommercialRuntimeBridge(self.db).sync_tool_call(tool_call.id)
            run = self.db.scalar(
                select(AgentRun).where(AgentRun.run_id == reservation.run_id)
            )
            if run is not None and str(run.status or "").strip().lower() in TERMINAL_RUN_STATUSES:
                CommercialRuntimeReservationService(self.db).release(
                    reservation.id,
                    reason_code="terminal_run_without_tool_call",
                    expired=True,
                    settled_at=when,
                )
                self.db.commit()
                return _result(
                    reservation,
                    reason_code="expired_without_business_call",
                    reason="运行已终止且没有真实工具调用，过期预占已安全释放。",
                )
            self.db.rollback()
            return _result(
                reservation,
                reason_code="expired_reservation_still_uncertain",
                reason="运行尚未终止或运行记录缺失，继续保留额度并等待可验证终态。",
                collecting=True,
                requires_reconciliation=True,
            )
        except Exception:
            self.db.rollback()
            return RuntimeMeteringResult(
                status="error",
                reason_code="reservation_reconciliation_failed",
                reason="过期预占补偿失败，额度继续保留并等待幂等重试。",
                tool_call_id=tool_call_id,
                run_id=run_id,
                tenant_id=tenant_id,
                business_call_occurred=False,
                requires_reconciliation=True,
            )


def _result(
    reservation: CommercialRuntimeReservation,
    *,
    reason_code: str,
    reason: str,
    collecting: bool = False,
    requires_reconciliation: bool = False,
) -> RuntimeMeteringResult:
    return RuntimeMeteringResult(
        status="skipped",
        reason_code=reason_code,
        reason=reason,
        tool_call_id=reservation.tool_call_id,
        run_id=reservation.run_id,
        tenant_id=reservation.tenant_id,
        entitlement_id=reservation.entitlement_id,
        quantity_basis=reservation.quantity_basis,
        collecting=collecting,
        business_call_occurred=False,
        requires_reconciliation=requires_reconciliation,
    )


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