from __future__ import annotations

from typing import Annotated

from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, status
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext, get_current_user, get_db
from app.api.pagination import PageNumber, PageSize, page_payload, wants_page
from app.schemas.budget import BudgetClaimAnalysisRead
from app.schemas.common import ErrorResponse, PaginatedResponse
from app.schemas.reimbursement import (
    ExpenseClaimActionResponse,
    ExpenseClaimAttachmentActionResponse,
    ExpenseClaimAttachmentRead,
    ExpenseClaimItemActionResponse,
    ExpenseClaimItemCreate,
    ExpenseClaimItemUpdate,
    ExpenseClaimRead,
    ExpenseClaimStandardAdjustmentPayload,
    ExpenseClaimSubmitPayload,
    ExpenseClaimUpdate,
    ReimbursementCreate,
    ReimbursementRead,
    TravelReimbursementCalculatorRequest,
    TravelReimbursementCalculatorResponse,
)
from app.services.budget import BudgetService
from app.services.expense_claim_attachment_commercial import (
    ExpenseClaimAttachmentCommercialAccessDenied,
)
from app.services.expense_claims import ExpenseClaimService
from app.services.reimbursement import ReimbursementService
from app.services.travel_reimbursement_calculator import TravelReimbursementCalculatorService

from .reimbursement_approval_actions import router as approval_action_router
from .reimbursement_pre_review import (
    RequestIdHeader,
    expense_claim_deletion_response,
    pre_review_expense_claim_or_http_error,
    submit_expense_claim_or_http_error,
)

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


@router.get(
    "",
    response_model=list[ReimbursementRead] | PaginatedResponse[ReimbursementRead],
    summary="查询报销申请列表",
    description="返回当前系统中的报销申请列表。",
)
def list_reimbursements(
    db: DbSession,
    page: PageNumber = None,
    page_size: PageSize = None,
) -> list[ReimbursementRead] | PaginatedResponse[ReimbursementRead]:
    service = ReimbursementService(db)
    if wants_page(page, page_size):
        return page_payload(service.list_reimbursements_page(page=page, page_size=page_size))
    return service.list_reimbursements()


@router.post(
    "",
    response_model=ReimbursementRead,
    status_code=status.HTTP_201_CREATED,
    summary="创建报销申请",
    description="创建一条新的报销申请记录，初始状态为 `draft`。",
)
def create_reimbursement(payload: ReimbursementCreate, db: DbSession) -> ReimbursementRead:
    return ReimbursementService(db).create_reimbursement(payload)


@router.post(
    "/travel-calculator",
    response_model=TravelReimbursementCalculatorResponse,
    summary="差旅报销标准测算",
    description="根据规则中心的差旅报销表、当前员工职级、出差天数与地点测算住宿和补贴参考金额。",
    responses={
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "测算入参或规则匹配失败。",
        }
    },
)
def calculate_travel_reimbursement(
    payload: TravelReimbursementCalculatorRequest,
    db: DbSession,
    current_user: CurrentUser,
) -> TravelReimbursementCalculatorResponse:
    try:
        return TravelReimbursementCalculatorService(db).calculate(payload, current_user)
    except ValueError as error:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error


@router.get(
    "/claims",
    response_model=list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead],
    summary="查询个人报销单列表",
    description="返回当前登录用户可见的真实个人报销单据列表。",
)
def list_expense_claims(
    db: DbSession,
    current_user: CurrentUser,
    page: PageNumber = None,
    page_size: PageSize = None,
) -> list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead]:
    service = ExpenseClaimService(db)
    if wants_page(page, page_size):
        return page_payload(service.list_claims_page(current_user, page=page, page_size=page_size))
    return service.list_claims(current_user)


@router.get(
    "/claims/approvals",
    response_model=list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead],
    summary="查询当前用户审批待办报销单列表",
    description="返回当前登录用户有权处理的待审批报销单据，不混入个人报销列表。",
)
def list_expense_claim_approvals(
    db: DbSession,
    current_user: CurrentUser,
    page: PageNumber = None,
    page_size: PageSize = None,
) -> list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead]:
    service = ExpenseClaimService(db)
    if wants_page(page, page_size):
        return page_payload(
            service.list_approval_claims_page(current_user, page=page, page_size=page_size)
        )
    return service.list_approval_claims(current_user)


@router.get(
    "/claims/archives",
    response_model=list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead],
    summary="查询归档中心报销单列表",
    description="返回公司已归档入账的报销单据，供财务与审计角色集中查阅。",
)
def list_archived_expense_claims(
    db: DbSession,
    current_user: CurrentUser,
    page: PageNumber = None,
    page_size: PageSize = None,
) -> list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead]:
    service = ExpenseClaimService(db)
    if wants_page(page, page_size):
        return page_payload(
            service.list_archived_claims_page(current_user, page=page, page_size=page_size)
        )
    return service.list_archived_claims(current_user)


@router.get(
    "/claims/{claim_id}",
    response_model=ExpenseClaimRead,
    summary="读取个人报销单详情",
    description="根据报销单主键读取真实报销详情与费用明细。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单不存在。",
        }
    },
)
def get_expense_claim(claim_id: str, db: DbSession, current_user: CurrentUser) -> ExpenseClaimRead:
    claim = ExpenseClaimService(db).get_claim(claim_id, current_user)
    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return claim


@router.get(
    "/claims/{claim_id}/budget-analysis",
    response_model=BudgetClaimAnalysisRead,
    summary="读取申请单预算分析",
    description="根据当前预算池、申请金额和预算管控模型，返回费用申请的预算影响和评分建议。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "单据不存在。",
        },
        status.HTTP_403_FORBIDDEN: {
            "model": ErrorResponse,
            "description": "当前用户无权查看预算分析。",
        },
    },
)
def get_expense_claim_budget_analysis(
    claim_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> BudgetClaimAnalysisRead:
    service = ExpenseClaimService(db)
    claim = service.get_claim(claim_id, current_user)
    if claim is None:
        if not service.can_view_budget_analysis(current_user):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="只有当前审核人、该部门预算监控员或高级财务人员可以查看预算分析。",
            )
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    if not service.can_view_budget_analysis(current_user, claim):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="只有当前审核人、该部门预算监控员或高级财务人员可以查看预算分析。",
        )
    return BudgetService(db).analyze_claim_budget(claim)


@router.patch(
    "/claims/{claim_id}",
    response_model=ExpenseClaimRead,
    summary="更新草稿报销单",
    description="更新草稿待提交报销单的主说明等草稿字段。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "报销单状态不允许更新。",
        },
    },
)
def update_expense_claim(
    claim_id: str,
    payload: ExpenseClaimUpdate,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimRead:
    service = ExpenseClaimService(db)
    try:
        claim = service.update_claim(
            claim_id=claim_id,
            payload=payload,
            current_user=current_user,
        )
    except ValueError as error:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from 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}/standard-adjustment",
    response_model=ExpenseClaimRead,
    summary="接受职级报销标准重算",
    description="在草稿报销单存在中高风险但提交人不补充异常说明时，按职级可报销标准重算实际报销金额。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "报销单状态不允许重算或入参不合法。",
        },
    },
)
def accept_expense_claim_standard_adjustment(
    claim_id: str,
    payload: ExpenseClaimStandardAdjustmentPayload,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimRead:
    service = ExpenseClaimService(db)
    try:
        claim = service.accept_standard_adjustment(
            claim_id=claim_id,
            payload=payload,
            current_user=current_user,
        )
    except ValueError as error:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error

    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return claim


@router.patch(
    "/claims/{claim_id}/items/{item_id}",
    response_model=ExpenseClaimRead,
    summary="更新草稿费用明细",
    description="更新草稿报销单中的单条费用明细。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单或费用明细不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "草稿状态校验失败或字段校验失败。",
        },
    },
)
def update_expense_claim_item(
    claim_id: str,
    item_id: str,
    payload: ExpenseClaimItemUpdate,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimRead:
    service = ExpenseClaimService(db)
    try:
        claim = service.update_claim_item(
            claim_id=claim_id,
            item_id=item_id,
            payload=payload,
            current_user=current_user,
        )
    except LookupError as error:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except ValueError as error:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from 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}/items",
    response_model=ExpenseClaimRead,
    summary="新增草稿费用明细",
    description="在草稿报销单中新增一条费用明细，供用户继续补充附件与字段。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "草稿状态校验失败或字段校验失败。",
        },
    },
)
def create_expense_claim_item(
    claim_id: str,
    payload: ExpenseClaimItemCreate | None,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimRead:
    service = ExpenseClaimService(db)
    try:
        claim = service.create_claim_item(
            claim_id=claim_id,
            payload=payload,
            current_user=current_user,
        )
    except ValueError as error:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error

    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return claim


@router.delete(
    "/claims/{claim_id}/items/{item_id}",
    response_model=ExpenseClaimItemActionResponse,
    summary="删除草稿费用明细",
    description="删除草稿报销单中的一条费用明细，并同步清理该行已上传的附件与 OCR 元数据。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单或费用明细不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "草稿状态校验失败。",
        },
    },
)
def delete_expense_claim_item(
    claim_id: str,
    item_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimItemActionResponse:
    service = ExpenseClaimService(db)
    try:
        payload = service.delete_claim_item(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
    except LookupError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except ValueError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
    except Exception:
        db.rollback()
        raise

    if payload is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return ExpenseClaimItemActionResponse(**payload)


@router.post(
    "/claims/{claim_id}/items/{item_id}/attachment",
    response_model=ExpenseClaimAttachmentActionResponse,
    summary="上传费用明细附件",
    description="为草稿费用明细上传真实附件文件，并返回附件元信息与 AI 校验结果。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单或费用明细不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "草稿状态校验失败或文件不合法。",
        },
    },
)
async def upload_expense_claim_item_attachment(
    claim_id: str,
    item_id: str,
    file: Annotated[UploadFile, File(description="待上传的附件文件。")],
    db: DbSession,
    current_user: CurrentUser,
    receipt_id: Annotated[
        str | None, Form(description="可选，来源于票据夹的持久化票据 ID。")
    ] = None,
    request_id: RequestIdHeader = None,
) -> ExpenseClaimAttachmentActionResponse:
    service = ExpenseClaimService(db)
    try:
        payload = service.upload_claim_item_attachment(
            claim_id=claim_id,
            item_id=item_id,
            filename=str(file.filename or "attachment.bin"),
            content=await file.read(),
            media_type=file.content_type,
            current_user=current_user,
            source_receipt_id=receipt_id or "",
            request_id=request_id or "",
        )
    except LookupError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except ExpenseClaimAttachmentCommercialAccessDenied as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail=str(error),
        ) from error
    except ValueError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
    except Exception:
        db.rollback()
        raise

    if payload is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return ExpenseClaimAttachmentActionResponse(**payload)


@router.get(
    "/claims/{claim_id}/items/{item_id}/attachment/meta",
    response_model=ExpenseClaimAttachmentRead,
    summary="读取费用明细附件元信息",
    description="返回当前费用明细已上传附件的文件信息与 AI 校验结果。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单、费用明细或附件不存在。",
        },
    },
)
def get_expense_claim_item_attachment_meta(
    claim_id: str,
    item_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimAttachmentRead:
    service = ExpenseClaimService(db)
    try:
        payload = service.get_claim_item_attachment_meta(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
    except LookupError as error:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except FileNotFoundError as error:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error

    if payload is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return ExpenseClaimAttachmentRead(**payload)


@router.get(
    "/claims/{claim_id}/items/{item_id}/attachment/preview",
    response_class=FileResponse,
    summary="读取费用明细附件预览资源",
    description="优先返回票据预览图；若无单独预览图，则回退到原附件内容。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单、费用明细或附件预览不存在。",
        },
    },
)
def get_expense_claim_item_attachment_preview(
    claim_id: str,
    item_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> FileResponse:
    service = ExpenseClaimService(db)
    try:
        payload = service.get_claim_item_attachment_preview_content(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
    except LookupError as error:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except FileNotFoundError as error:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error

    if payload is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")

    file_path, media_type, filename = payload
    return FileResponse(file_path, media_type=media_type, filename=filename)


@router.get(
    "/claims/{claim_id}/items/{item_id}/attachment",
    response_class=FileResponse,
    summary="读取费用明细附件内容",
    description="用于详情页预览当前费用明细已上传的附件文件。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单、费用明细或附件不存在。",
        },
    },
)
def get_expense_claim_item_attachment(
    claim_id: str,
    item_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> FileResponse:
    service = ExpenseClaimService(db)
    try:
        payload = service.get_claim_item_attachment_content(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
    except LookupError as error:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except FileNotFoundError as error:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error

    if payload is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")

    file_path, media_type, filename = payload
    return FileResponse(file_path, media_type=media_type, filename=filename)


@router.delete(
    "/claims/{claim_id}/items/{item_id}/attachment",
    response_model=ExpenseClaimAttachmentActionResponse,
    summary="删除费用明细附件",
    description="删除草稿费用明细当前已上传的附件文件，并清空票据关联。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单、费用明细或附件不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "当前状态不允许删除附件。",
        },
    },
)
def delete_expense_claim_item_attachment(
    claim_id: str,
    item_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> ExpenseClaimAttachmentActionResponse:
    service = ExpenseClaimService(db)
    try:
        payload = service.delete_claim_item_attachment(
            claim_id=claim_id,
            item_id=item_id,
            current_user=current_user,
        )
    except LookupError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except FileNotFoundError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except ValueError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
    except Exception:
        db.rollback()
        raise

    if payload is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")
    return ExpenseClaimAttachmentActionResponse(**payload)


@router.post(
    "/claims/{claim_id}/pre-review",
    response_model=ExpenseClaimRead,
    summary="执行报销单 AI 预审",
    description="只执行 AI 预审并回写风险结果，不提交到审批流程。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "草稿信息不完整或状态不允许预审。",
        },
    },
)
def pre_review_expense_claim(
    claim_id: str,
    db: DbSession,
    current_user: CurrentUser,
    request_id: RequestIdHeader = None,
) -> ExpenseClaimRead:
    return pre_review_expense_claim_or_http_error(
        ExpenseClaimService(db),
        claim_id,
        current_user,
        request_id,
    )


@router.post(
    "/claims/{claim_id}/submit",
    response_model=ExpenseClaimRead,
    summary="提交个人报销草稿",
    description="校验草稿信息完整性后，将报销单提交到审批流程。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "草稿信息不完整或状态不允许提交。",
        },
    },
)
def submit_expense_claim(
    claim_id: str,
    db: DbSession,
    current_user: CurrentUser,
    payload: ExpenseClaimSubmitPayload | None = None,
) -> ExpenseClaimRead:
    return submit_expense_claim_or_http_error(
        ExpenseClaimService(db),
        claim_id,
        current_user,
        payload,
    )


@router.delete(
    "/claims/{claim_id}",
    response_model=ExpenseClaimActionResponse,
    summary="删除报销单",
    description="申请人可删除自己的草稿、待补充或退回单据（含申请单和报销单）；系统管理员可删除单据；已归档单据仅系统管理员可删除。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销单不存在。",
        },
        status.HTTP_400_BAD_REQUEST: {
            "model": ErrorResponse,
            "description": "当前用户或单据状态不允许删除。",
        },
    },
)
def delete_expense_claim(
    claim_id: str, db: DbSession, current_user: CurrentUser
) -> ExpenseClaimActionResponse:
    service = ExpenseClaimService(db)
    try:
        claim = service.delete_claim(claim_id, current_user)
    except ValueError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
    except Exception:
        db.rollback()
        raise

    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Claim not found")

    return expense_claim_deletion_response(claim)


@router.get(
    "/{request_id}",
    response_model=ReimbursementRead,
    summary="读取报销申请详情",
    description="根据报销申请主键读取单据详情。",
    responses={
        status.HTTP_404_NOT_FOUND: {
            "model": ErrorResponse,
            "description": "报销申请不存在。",
        }
    },
)
def get_reimbursement(request_id: str, db: DbSession) -> ReimbursementRead:
    request = ReimbursementService(db).get_reimbursement(request_id)
    if request is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Request not found")
    return request
