from __future__ import annotations

import hashlib
import json
import uuid
from collections.abc import Mapping, Sequence
from datetime import UTC, datetime
from typing import Any

from pydantic import ValidationError
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.approval_task import ApprovalTask, ApprovalTaskEvent
from app.schemas.approval_task import (
    ApprovalTaskEventRead,
    ApprovalTaskMutationRead,
    ApprovalTaskRead,
)
from app.services.approval_task_access_policy import ApprovalTaskPermission


class ApprovalTaskConflictError(ValueError):
    pass


class ApprovalTaskVersionConflictError(ApprovalTaskConflictError):
    def __init__(self, current_version: int) -> None:
        self.current_version = current_version
        super().__init__(f"审批任务版本已变化，当前版本为 {current_version}，请刷新后重试。")


class ApprovalTaskIdempotencyConflictError(ApprovalTaskConflictError):
    pass


def normalize_actor_id(value: object) -> str:
    return str(value or "").strip().casefold() or "anonymous"


def payload_fingerprint(
    *,
    task_id: str,
    actor_id: str,
    event_type: str,
    payload: Mapping[str, Any],
) -> str:
    canonical = json.dumps(
        {
            "task_id": str(task_id),
            "actor_id": normalize_actor_id(actor_id),
            "event_type": str(event_type).strip(),
            "payload": dict(payload),
        },
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        default=_json_default,
    )
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


class ApprovalTaskEventProtocol:
    """审批任务 append-only 事件与不可变响应重放。"""

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

    def find_replay(
        self,
        *,
        tenant_id: str,
        actor_id: str,
        request_id: str,
        fingerprint: str,
    ) -> ApprovalTaskMutationRead | None:
        event = self.db.scalar(
            select(ApprovalTaskEvent).where(
                ApprovalTaskEvent.tenant_id == tenant_id,
                ApprovalTaskEvent.actor_id == normalize_actor_id(actor_id),
                ApprovalTaskEvent.request_id == str(request_id).strip(),
            )
        )
        if event is None:
            return None
        if str(event.payload_fingerprint) != fingerprint:
            raise ApprovalTaskIdempotencyConflictError(
                "request_id 已被不同的审批任务动作使用。"
            )
        response = self._response_from_event(event)
        return response.model_copy(update={"replayed": True})

    def append_event(
        self,
        *,
        task: ApprovalTask,
        event_type: str,
        actor_id: str,
        actor_name: str,
        actor_type: str,
        request_id: str,
        expected_task_version: int,
        payload: Mapping[str, Any],
        before_json: Mapping[str, Any],
        related_tasks: Sequence[ApprovalTask] = (),
        permission: ApprovalTaskPermission | None = None,
        approval_action_ledger_id: str | None = None,
        business_event_id: str | None = None,
        correlation_id: str | None = None,
        causation_id: str | None = None,
        occurred_at: datetime | None = None,
    ) -> tuple[ApprovalTaskEvent, ApprovalTaskMutationRead]:
        normalized_actor = normalize_actor_id(actor_id)
        normalized_request = str(request_id or "").strip()
        if not normalized_request:
            raise ValueError("审批任务事件 request_id 不能为空。")
        fingerprint = payload_fingerprint(
            task_id=task.id,
            actor_id=normalized_actor,
            event_type=event_type,
            payload=payload,
        )
        event = ApprovalTaskEvent(
            id=str(uuid.uuid4()),
            tenant_id=task.tenant_id,
            task_id=task.id,
            node_instance_id=task.node_instance_id,
            event_type=str(event_type).strip(),
            actor_id=normalized_actor,
            actor_name=str(actor_name or actor_id or "anonymous").strip() or "anonymous",
            actor_type=str(actor_type or "user").strip(),
            request_id=normalized_request,
            expected_task_version=int(expected_task_version),
            result_task_version=int(task.version),
            payload_fingerprint=fingerprint,
            payload_json=dict(payload),
            before_json=dict(before_json),
            after_json=task_state(task),
            response_json={},
            approval_action_ledger_id=approval_action_ledger_id,
            business_event_id=business_event_id,
            correlation_id=correlation_id,
            causation_id=causation_id,
        )
        event.occurred_at = occurred_at or datetime.now(UTC)
        response = ApprovalTaskMutationRead(
            task=task_read(task, permission=permission),
            event=ApprovalTaskEventRead.model_validate(event),
            related_tasks=[task_read(item) for item in related_tasks],
            replayed=False,
        )
        event.response_json = response.model_dump(mode="json")
        self.db.add(event)
        return event, response

    def _response_from_event(self, event: ApprovalTaskEvent) -> ApprovalTaskMutationRead:
        try:
            response = ApprovalTaskMutationRead.model_validate(event.response_json)
        except (TypeError, ValueError, ValidationError) as error:
            raise ApprovalTaskConflictError(
                "审批任务事件缺少可验证的原始响应快照，请刷新后重试。"
            ) from error
        if (
            response.event.id != event.id
            or response.event.task_id != event.task_id
            or response.event.request_id != event.request_id
            or response.event.result_task_version != event.result_task_version
            or response.task.id != event.task_id
            or response.task.tenant_id != event.tenant_id
            or response.task.version != event.result_task_version
        ):
            raise ApprovalTaskConflictError("审批任务事件响应快照身份校验失败。")
        return response


def task_read(
    task: ApprovalTask,
    *,
    permission: ApprovalTaskPermission | None = None,
) -> ApprovalTaskRead:
    update: dict[str, Any] = {}
    if permission is not None:
        update = {
            "can_act": permission.can_act,
            "available_actions": list(permission.available_actions),
            "read_only_reason": permission.read_only_reason,
        }
    return ApprovalTaskRead.model_validate(task).model_copy(update=update)


def task_state(task: ApprovalTask) -> dict[str, Any]:
    return {
        "status": task.status,
        "decision": task.decision,
        "opinion": task.opinion,
        "version": task.version,
        "coordination_mode": task.coordination_mode,
        "owner_kind": task.owner_kind,
        "owner_key": task.owner_key,
        "owner_employee_id": task.owner_employee_id,
        "owner_name": task.owner_name,
        "assignee_kind": task.assignee_kind,
        "assignee_key": task.assignee_key,
        "assignee_employee_id": task.assignee_employee_id,
        "assignee_name": task.assignee_name,
        "delegated_by": task.delegated_by,
        "delegation_expires_at": _json_default(task.delegation_expires_at),
        "claim_status_snapshot": task.claim_status_snapshot,
        "claim_stage_snapshot": task.claim_stage_snapshot,
        "activated_at": _json_default(task.activated_at),
        "completed_at": _json_default(task.completed_at),
        "cancelled_at": _json_default(task.cancelled_at),
        "escalation_level": task.escalation_level,
        "escalated_at": _json_default(task.escalated_at),
        "next_escalation_at": _json_default(task.next_escalation_at),
        "projection_updated_at": _json_default(task.projection_updated_at),
    }


def _json_default(value: object) -> object:
    if isinstance(value, datetime):
        return value.isoformat()
    return value
