from __future__ import annotations

from typing import Annotated, NoReturn

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext, get_current_user, get_db
from app.models.approval_task import ApprovalTask, ApprovalTaskEvent
from app.schemas.approval_task import (
    ApprovalTaskActionBase,
    ApprovalTaskAssignmentAction,
    ApprovalTaskBatchApproveCreate,
    ApprovalTaskBatchApproveRead,
    ApprovalTaskCandidateRead,
    ApprovalTaskDecisionAction,
    ApprovalTaskDecisionRead,
    ApprovalTaskListRead,
    ApprovalTaskMutationRead,
    ApprovalTaskParticipantsAction,
    ApprovalTaskQueueItemRead,
    ApprovalTaskReturnAction,
)
from app.schemas.reimbursement import ExpenseClaimRead
from app.services.approval_action_protocol import ApprovalActionConflictError
from app.services.approval_task_access_policy import ApprovalTaskPermissionError
from app.services.approval_task_actions import ApprovalTaskActionService
from app.services.approval_task_batch import ApprovalTaskBatchService
from app.services.approval_task_protocol import ApprovalTaskConflictError
from app.services.approval_task_query import ApprovalTaskQueryService
from app.services.expense_claim_risk_gate import ExpenseClaimRiskBlockedError
from app.services.expense_claim_tenant_scope import ExpenseClaimTenantScopeMixin
from app.services.expense_claims import ExpenseClaimService

router = APIRouter(prefix="/approval-tasks")
DbSession = Annotated[Session, Depends(get_db)]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]


@router.get("", response_model=ApprovalTaskListRead, summary="查询个人审批任务队列")
def list_approval_tasks(
    db: DbSession,
    current_user: CurrentUser,
    page: Annotated[int, Query(ge=1)] = 1,
    page_size: Annotated[int, Query(ge=1, le=200)] = 20,
    task_status: Annotated[str, Query(alias="status")] = "pending",
    risk_level: str | None = None,
    sla_state: str | None = None,
    node_key: str | None = None,
    task_kind: str | None = None,
    batch_eligible: bool | None = None,
    assignee: Annotated[str | None, Query(max_length=100)] = None,
    sort: str = "priority_desc",
    keyword: Annotated[str | None, Query(max_length=100)] = None,
) -> ApprovalTaskListRead:
    try:
        return ApprovalTaskQueryService(db).list_tasks(
            current_user,
            page=page,
            page_size=page_size,
            status=task_status,
            risk_level=risk_level,
            sla_state=sla_state,
            node_key=node_key,
            task_kind=task_kind,
            batch_eligible=batch_eligible,
            assignee=assignee,
            sort=sort,
            keyword=keyword,
        )
    except ValueError as error:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error


@router.get(
    "/{task_id}",
    response_model=ApprovalTaskQueueItemRead,
    summary="查询审批任务详情",
)
def get_approval_task(
    task_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskQueueItemRead:
    item = ApprovalTaskQueryService(db).get_task(task_id, current_user)
    if item is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
    return item


@router.get(
    "/{task_id}/candidates",
    response_model=list[ApprovalTaskCandidateRead],
    summary="查询当前节点合格处理人",
)
def list_approval_task_candidates(
    task_id: str,
    db: DbSession,
    current_user: CurrentUser,
    action: str | None = None,
    keyword: Annotated[str | None, Query(max_length=100)] = None,
    limit: Annotated[int, Query(ge=1, le=100)] = 50,
) -> list[ApprovalTaskCandidateRead]:
    try:
        return ApprovalTaskActionService(db).candidates(
            task_id,
            current_user,
            action=action,
            keyword=keyword,
            limit=limit,
        )
    except (LookupError, ApprovalTaskPermissionError) as error:
        _raise_task_error(error)


@router.post(
    "/{task_id}/delegate",
    response_model=ApprovalTaskMutationRead,
    summary="委托审批任务",
)
def delegate_approval_task(
    task_id: str,
    payload: ApprovalTaskAssignmentAction,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskMutationRead:
    return _assignment_action(task_id, payload, db, current_user, action="delegate")


@router.post(
    "/{task_id}/delegation/revoke",
    response_model=ApprovalTaskMutationRead,
    summary="撤销审批委托",
)
def revoke_approval_task_delegation(
    task_id: str,
    payload: ApprovalTaskActionBase,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskMutationRead:
    try:
        return ApprovalTaskActionService(db).revoke_delegation(task_id, current_user, payload)
    except (LookupError, ValueError) as error:
        _raise_task_error(error)


@router.post(
    "/{task_id}/transfer",
    response_model=ApprovalTaskMutationRead,
    summary="永久转交审批任务",
)
def transfer_approval_task(
    task_id: str,
    payload: ApprovalTaskAssignmentAction,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskMutationRead:
    return _assignment_action(task_id, payload, db, current_user, action="transfer")


@router.post(
    "/{task_id}/add-sign",
    response_model=ApprovalTaskMutationRead,
    summary="发起顺序加签",
)
def add_sign_approval_task(
    task_id: str,
    payload: ApprovalTaskParticipantsAction,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskMutationRead:
    return _participants_action(task_id, payload, db, current_user, action="add_sign")


@router.post(
    "/{task_id}/countersign",
    response_model=ApprovalTaskMutationRead,
    summary="发起并行会签",
)
def countersign_approval_task(
    task_id: str,
    payload: ApprovalTaskParticipantsAction,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskMutationRead:
    return _participants_action(task_id, payload, db, current_user, action="countersign")


@router.post(
    "/{task_id}/escalate",
    response_model=ApprovalTaskMutationRead,
    summary="升级超时审批任务",
)
def escalate_approval_task(
    task_id: str,
    payload: ApprovalTaskActionBase,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskMutationRead:
    try:
        return ApprovalTaskActionService(db).escalate(task_id, current_user, payload)
    except (LookupError, ValueError) as error:
        _raise_task_error(error)


@router.post(
    "/{task_id}/approve",
    response_model=ApprovalTaskDecisionRead,
    summary="通过审批任务",
)
def approve_approval_task(
    task_id: str,
    payload: ApprovalTaskDecisionAction,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskDecisionRead:
    try:
        task = _task_or_404(db, task_id, current_user)
        if task.task_kind == "root":
            claim = ExpenseClaimService(db).approve_claim(
                task.claim_id,
                current_user,
                opinion=payload.opinion or payload.reason,
                request_id=payload.request_id,
                expected_status=task.claim_status_snapshot,
                expected_approval_stage=task.claim_stage_snapshot,
                task_id=task.id,
                expected_task_version=payload.expected_task_version,
            )
            if claim is None:
                raise LookupError("Claim not found.")
            mutation = _mutation_for_request(db, task, payload.request_id)
            if isinstance(claim, ExpenseClaimRead):
                mutation = mutation.model_copy(update={"replayed": True})
            claim_read = (
                claim
                if isinstance(claim, ExpenseClaimRead)
                else ExpenseClaimRead.model_validate(claim)
            )
        else:
            mutation = ApprovalTaskActionService(db).approve_child(
                task.id,
                current_user,
                payload,
            )
            queue_item = ApprovalTaskQueryService(db).get_task(task.id, current_user)
            if queue_item is None:
                raise LookupError("Task not found.")
            claim_read = queue_item.claim
        return ApprovalTaskDecisionRead(mutation=mutation, claim=claim_read)
    except (LookupError, ValueError) as error:
        _raise_task_error(error)


@router.post(
    "/{task_id}/return",
    response_model=ApprovalTaskDecisionRead,
    summary="退回审批任务关联单据",
)
def return_approval_task(
    task_id: str,
    payload: ApprovalTaskReturnAction,
    db: DbSession,
    current_user: CurrentUser,
) -> ApprovalTaskDecisionRead:
    try:
        task = _task_or_404(db, task_id, current_user)
        claim = ExpenseClaimService(db).return_claim(
            task.claim_id,
            current_user,
            reason=payload.reason,
            reason_codes=payload.reason_codes,
            request_id=payload.request_id,
            expected_status=task.claim_status_snapshot,
            expected_approval_stage=task.claim_stage_snapshot,
            task_id=task.id,
            expected_task_version=payload.expected_task_version,
        )
        if claim is None:
            raise LookupError("Claim not found.")
        mutation = _mutation_for_request(db, task, payload.request_id)
        if isinstance(claim, ExpenseClaimRead):
            mutation = mutation.model_copy(update={"replayed": True})
        claim_read = (
            claim
            if isinstance(claim, ExpenseClaimRead)
            else ExpenseClaimRead.model_validate(claim)
        )
        return ApprovalTaskDecisionRead(mutation=mutation, claim=claim_read)
    except (LookupError, ValueError) as error:
        _raise_task_error(error)


@router.post(
    "/batch-approve",
    response_model=ApprovalTaskBatchApproveRead,
    summary="安全批量审批任务",
)
def batch_approve_tasks(
    payload: ApprovalTaskBatchApproveCreate,
    current_user: CurrentUser,
) -> ApprovalTaskBatchApproveRead:
    return ApprovalTaskBatchService().approve(payload, current_user)


def _assignment_action(
    task_id: str,
    payload: ApprovalTaskAssignmentAction,
    db: Session,
    current_user: CurrentUserContext,
    *,
    action: str,
) -> ApprovalTaskMutationRead:
    try:
        return ApprovalTaskActionService(db).assign(
            task_id,
            current_user,
            action=action,
            payload=payload,
        )
    except (LookupError, ValueError) as error:
        _raise_task_error(error)


def _participants_action(
    task_id: str,
    payload: ApprovalTaskParticipantsAction,
    db: Session,
    current_user: CurrentUserContext,
    *,
    action: str,
) -> ApprovalTaskMutationRead:
    try:
        return ApprovalTaskActionService(db).add_participants(
            task_id,
            current_user,
            action=action,
            payload=payload,
        )
    except (LookupError, ValueError) as error:
        _raise_task_error(error)


def _task_or_404(
    db: Session,
    task_id: str,
    current_user: CurrentUserContext,
) -> ApprovalTask:
    tenant = ExpenseClaimTenantScopeMixin.normalize_tenant_id(current_user.tenant_id)
    task = db.scalar(
        select(ApprovalTask).where(
            ApprovalTask.tenant_id == tenant,
            ApprovalTask.id == str(task_id).strip(),
        )
    )
    if task is None:
        raise LookupError("Task not found.")
    return task


def _mutation_for_request(
    db: Session,
    task: ApprovalTask,
    request_id: str,
) -> ApprovalTaskMutationRead:
    event = db.scalar(
        select(ApprovalTaskEvent).where(
            ApprovalTaskEvent.tenant_id == task.tenant_id,
            ApprovalTaskEvent.task_id == task.id,
            ApprovalTaskEvent.request_id == request_id,
        )
    )
    if event is None:
        raise ApprovalTaskConflictError("审批任务审计事件缺失。")
    try:
        return ApprovalTaskMutationRead.model_validate(event.response_json)
    except ValueError as error:
        raise ApprovalTaskConflictError("审批任务审计响应快照无效。") from error


def _raise_task_error(error: Exception) -> NoReturn:
    if isinstance(error, LookupError):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    if isinstance(error, ApprovalTaskPermissionError):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(error)) from error
    if isinstance(error, ExpenseClaimRiskBlockedError):
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail={"code": "APPROVAL_BLOCKED_BY_OPEN_HIGH_RISK", "message": str(error)},
        ) from error
    if isinstance(error, (ApprovalActionConflictError, ApprovalTaskConflictError)):
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error
    raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
