from __future__ import annotations

from datetime import UTC, datetime, timedelta

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.approval_task import ApprovalTask
from app.models.financial_record import ExpenseClaim
from app.services.approval_task_protocol import ApprovalTaskEventProtocol, task_state
from app.services.expense_claim_tenant_scope import ExpenseClaimTenantScopeMixin

MAX_ESCALATION_LEVEL = 3


class ApprovalTaskSlaService:
    """按节点进入时间快照升级超时任务，不改变审批权限和 Claim 状态。"""

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

    def escalate_due_tasks(
        self,
        *,
        now: datetime | None = None,
        limit: int = 100,
    ) -> list[str]:
        checked_at = _as_utc(now or datetime.now(UTC))
        locators = list(
            self.db.execute(
                select(
                    ApprovalTask.id,
                    ApprovalTask.claim_id,
                    ApprovalTask.tenant_id,
                )
                .where(
                    ApprovalTask.status.in_(("waiting", "pending")),
                    ApprovalTask.next_escalation_at.is_not(None),
                    ApprovalTask.next_escalation_at <= checked_at,
                    ApprovalTask.escalation_level < MAX_ESCALATION_LEVEL,
                )
                .order_by(
                    ApprovalTask.next_escalation_at.asc(),
                    ApprovalTask.priority_score.desc(),
                    ApprovalTask.id.asc(),
                )
                .limit(min(max(int(limit), 1), 500))
            ).all()
        )
        escalated: list[str] = []
        for task_id, claim_id, tenant_id in locators:
            if self._escalate_one(
                task_id=str(task_id),
                claim_id=str(claim_id),
                tenant_id=str(tenant_id),
                now=checked_at,
            ):
                escalated.append(str(task_id))
        return escalated

    def _escalate_one(
        self,
        *,
        task_id: str,
        claim_id: str,
        tenant_id: str,
        now: datetime,
    ) -> bool:
        claim_statement = select(ExpenseClaim).where(
            ExpenseClaim.id == claim_id,
            ExpenseClaimTenantScopeMixin.build_claim_tenant_condition(tenant_id),
        )
        task_statement = select(ApprovalTask).where(
            ApprovalTask.tenant_id == tenant_id,
            ApprovalTask.id == task_id,
        )
        bind = self.db.get_bind()
        if bind is not None and bind.dialect.name == "postgresql":
            claim_statement = claim_statement.with_for_update()
            task_statement = task_statement.with_for_update()
        claim = self.db.scalar(claim_statement.execution_options(populate_existing=True))
        if claim is None:
            return False
        task = self.db.scalar(task_statement.execution_options(populate_existing=True))
        if (
            task is None
            or task.status not in {"waiting", "pending"}
            or task.next_escalation_at is None
            or _as_utc(task.next_escalation_at) > now
            or task.escalation_level >= MAX_ESCALATION_LEVEL
        ):
            return False

        before = task_state(task)
        expected = task.version
        task.escalation_level += 1
        task.escalated_at = now
        task.version += 1
        task.updated_at = now
        task.next_escalation_at = (
            None
            if task.escalation_level >= MAX_ESCALATION_LEVEL
            else now + timedelta(hours=4 * (2 ** (task.escalation_level - 1)))
        )
        task.priority_score = min(100, int(task.priority_score) + 10)
        task.priority_tier = "urgent" if task.priority_score >= 65 else "high"
        reasons = list(task.priority_reasons_json or [])
        reasons.append(
            {
                "code": f"sla_escalated_l{task.escalation_level}",
                "label": f"审批超时已升级至 L{task.escalation_level}",
                "weight": 10,
                "tone": "danger",
            }
        )
        task.priority_reasons_json = reasons
        self.events.append_event(
            task=task,
            event_type="sla_escalated",
            actor_id="system",
            actor_name="审批 SLA 调度器",
            actor_type="system",
            request_id=f"sla:{task.id}:{task.version}",
            expected_task_version=expected,
            payload={
                "level": task.escalation_level,
                "due_at": task.due_at.isoformat(),
                "next_escalation_at": (
                    task.next_escalation_at.isoformat()
                    if task.next_escalation_at is not None
                    else None
                ),
            },
            before_json=before,
            occurred_at=now,
        )
        # 项目 Session 关闭了 autoflush；同一事务再次扫描前必须让版本、
        # 下次升级时间和唯一事件版本同时可见。
        self.db.flush()
        return True


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