from __future__ import annotations

from typing import Annotated, NoReturn

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

from app.api.deps import CurrentUserContext, get_current_user, get_db
from app.schemas.common import ErrorResponse
from app.schemas.reimbursement import (
    ExpenseClaimApprovalPayload,
    ExpenseClaimPaymentPayload,
    ExpenseClaimRead,
    ExpenseClaimReturnPayload,
)
from app.services.approval_action_protocol import ApprovalActionConflictError
from app.services.approval_task_access_policy import ApprovalTaskPermissionError
from app.services.approval_task_protocol import ApprovalTaskConflictError
from app.services.expense_claim_risk_gate import ExpenseClaimRiskBlockedError
from app.services.expense_claims import ExpenseClaimService

router = APIRouter()
DbSession = Annotated[Session, Depends(get_db)]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]


def _raise_action_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, ExpenseClaimRiskBlockedError):
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail={
                "code": "APPROVAL_BLOCKED_BY_OPEN_HIGH_RISK",
                "message": str(error),
                "observations": [
                    {
                        "id": item.observation_id,
                        "title": item.title,
                        "risk_level": item.risk_level,
                        "adjudication": item.adjudication,
                        "lifecycle_status": item.lifecycle_status,
                    }
                    for item in error.blockers
                ],
            },
        ) from error
    if isinstance(error, ApprovalActionConflictError):
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, 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, 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


@router.post(
    "/claims/{claim_id}/return",
    response_model=ExpenseClaimRead,
    summary="退回报销单",
    description="按请求幂等键和预期状态安全退回当前节点的单据。",
    responses={
        status.HTTP_404_NOT_FOUND: {"model": ErrorResponse, "description": "报销单不存在。"},
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "当前用户或单据状态不允许退回。",
        },
        status.HTTP_409_CONFLICT: {
            "model": ErrorResponse,
            "description": "请求键冲突或单据状态/审批节点已经变化。",
        },
    },
)
def return_expense_claim(
    claim_id: str,
    payload: ExpenseClaimReturnPayload,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimRead:
    try:
        claim = ExpenseClaimService(db).return_claim(
            claim_id,
            current_user,
            reason=payload.reason,
            reason_codes=payload.reason_codes,
            request_id=payload.request_id,
            expected_status=payload.expected_status,
            expected_approval_stage=payload.expected_approval_stage,
            task_id=payload.task_id,
            expected_task_version=payload.expected_task_version,
        )
    except (LookupError, ValueError) as error:
        _raise_action_error(error)
    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return claim


@router.post(
    "/claims/{claim_id}/approve",
    response_model=ExpenseClaimRead,
    summary="审批通过单据",
    description="按请求幂等键和预期状态安全完成当前审批节点。",
    responses={
        status.HTTP_404_NOT_FOUND: {"model": ErrorResponse, "description": "单据不存在。"},
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "当前用户或单据状态不允许审批通过。",
        },
        status.HTTP_409_CONFLICT: {
            "model": ErrorResponse,
            "description": "请求键冲突或单据状态/审批节点已经变化。",
        },
    },
)
def approve_expense_claim(
    claim_id: str,
    payload: ExpenseClaimApprovalPayload,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimRead:
    try:
        claim = ExpenseClaimService(db).approve_claim(
            claim_id,
            current_user,
            opinion=payload.opinion,
            request_id=payload.request_id,
            expected_status=payload.expected_status,
            expected_approval_stage=payload.expected_approval_stage,
            task_id=payload.task_id,
            expected_task_version=payload.expected_task_version,
        )
    except (LookupError, ValueError) as error:
        _raise_action_error(error)
    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return claim


@router.post(
    "/claims/{claim_id}/pay",
    response_model=ExpenseClaimRead,
    summary="确认报销单已付款",
    description="按请求幂等键和预期状态安全确认待付款报销单已付款。",
    responses={
        status.HTTP_404_NOT_FOUND: {"model": ErrorResponse, "description": "单据不存在。"},
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "当前用户或单据状态不允许确认付款。",
        },
        status.HTTP_409_CONFLICT: {
            "model": ErrorResponse,
            "description": "请求键冲突或单据状态/审批节点已经变化。",
        },
    },
)
def pay_expense_claim(
    claim_id: str,
    payload: ExpenseClaimPaymentPayload,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimRead:
    try:
        claim = ExpenseClaimService(db).mark_claim_paid(
            claim_id,
            current_user,
            request_id=payload.request_id,
            expected_status=payload.expected_status,
            expected_approval_stage=payload.expected_approval_stage,
        )
    except (LookupError, ValueError) as error:
        _raise_action_error(error)
    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return claim
