from __future__ import annotations

from fastapi import APIRouter, Depends, File, Form, HTTPException, Request, UploadFile
from fastapi.responses import FileResponse

from ...core.logger import AppLogger
from ...core.permissions import (
    PERMISSION_MONTHLY_PAYROLL_CALCULATE,
    PERMISSION_MONTHLY_PAYROLL_EXPORT,
    PERMISSION_MONTHLY_PAYROLL_LOCK,
    PERMISSION_MONTHLY_PAYROLL_VIEW,
)
from ...database.orm import MonthlyPayrollRunORM, PayrollExceptionORM, UserORM
from ...integrations.dingtalk import DingTalkError
from ...services import MonthlyPayrollDetailData, MonthlyPayrollService, OperationLogService
from ..dependencies import get_monthly_payroll_service, get_operation_log_service, require_permission
from ..request_meta import operation_request_meta
from ..schemas.monthly_payroll import (
    MonthlyPayrollCreateRequest,
    MonthlyPayrollDetailResponse,
    MonthlyPayrollRunResponse,
    PayrollExceptionItem,
)
from ..schemas.payroll import PayrollResultDTO

router = APIRouter(prefix="/api/payroll/monthly", tags=["monthly-payroll"])
logger = AppLogger.get_logger(__name__)


@router.get("/runs", response_model=list[MonthlyPayrollRunResponse])
def list_monthly_runs(
    month: str | None = None,
    _: UserORM = Depends(require_permission(PERMISSION_MONTHLY_PAYROLL_VIEW)),
    service: MonthlyPayrollService = Depends(get_monthly_payroll_service),
) -> list[MonthlyPayrollRunResponse]:
    """按月份查看月度核算批次。"""
    return [_run_response(run) for run in service.list_runs(month)]


@router.get("/runs/{run_id}", response_model=MonthlyPayrollDetailResponse)
def get_monthly_run(
    http_request: Request,
    run_id: int,
    current_user: UserORM = Depends(require_permission(PERMISSION_MONTHLY_PAYROLL_VIEW)),
    service: MonthlyPayrollService = Depends(get_monthly_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> MonthlyPayrollDetailResponse:
    """查看月度核算批次明细、工资结果和异常清单。"""
    try:
        detail = service.get_detail(run_id)
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    operation_logs.record(
        actor=current_user,
        module="monthly_payroll",
        action="monthly_payroll.view",
        target_type="monthly_payroll_run",
        target_id=str(run_id),
        status="success",
        detail=f"查看月度核算 run_id={run_id}",
        **operation_request_meta(http_request),
    )
    return _detail_response(detail)


@router.post("/excel", response_model=MonthlyPayrollDetailResponse)
def calculate_monthly_from_excel(
    http_request: Request,
    file: UploadFile = File(...),
    salary_month: str = Form(...),
    export_excel: bool = Form(default=True),
    current_user: UserORM = Depends(require_permission(PERMISSION_MONTHLY_PAYROLL_CALCULATE)),
    service: MonthlyPayrollService = Depends(get_monthly_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> MonthlyPayrollDetailResponse:
    """导入 Excel 并生成月度正式核算批次。"""
    try:
        detail = service.calculate_from_excel(
            salary_month=salary_month,
            filename=file.filename or "attendance.xlsx",
            stream=file.file,
            created_by=current_user.username,
            export_excel=export_excel,
        )
    except ValueError as exc:
        _record_failed(operation_logs, current_user, http_request, "monthly_payroll.excel.import", str(exc))
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    operation_logs.record(
        actor=current_user,
        module="monthly_payroll",
        action="monthly_payroll.excel.import",
        target_type="monthly_payroll_run",
        target_id=str(detail.run.id),
        status="success",
        detail=f"Excel月度核算成功 salary_month={salary_month} employee_count={detail.run.employee_count}",
        **operation_request_meta(http_request),
    )
    return _detail_response(detail)


@router.post("/dingtalk", response_model=MonthlyPayrollDetailResponse)
async def calculate_monthly_from_dingtalk(
    http_request: Request,
    request: MonthlyPayrollCreateRequest,
    current_user: UserORM = Depends(require_permission(PERMISSION_MONTHLY_PAYROLL_CALCULATE)),
    service: MonthlyPayrollService = Depends(get_monthly_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> MonthlyPayrollDetailResponse:
    """按员工档案中的钉钉 userId 自动同步整月考勤并生成核算批次。"""
    try:
        detail = await service.calculate_from_dingtalk(
            salary_month=request.salary_month,
            created_by=current_user.username,
            export_excel=request.export_excel,
        )
    except ValueError as exc:
        _record_failed(operation_logs, current_user, http_request, "monthly_payroll.dingtalk.calculate", str(exc))
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except DingTalkError as exc:
        _record_failed(operation_logs, current_user, http_request, "monthly_payroll.dingtalk.calculate", str(exc))
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    operation_logs.record(
        actor=current_user,
        module="monthly_payroll",
        action="monthly_payroll.dingtalk.calculate",
        target_type="monthly_payroll_run",
        target_id=str(detail.run.id),
        status="success",
        detail=f"钉钉月度核算成功 salary_month={request.salary_month} employee_count={detail.run.employee_count}",
        **operation_request_meta(http_request),
    )
    return _detail_response(detail)


@router.post("/runs/{run_id}/recalculate", response_model=MonthlyPayrollDetailResponse)
async def recalculate_monthly_run(
    http_request: Request,
    run_id: int,
    current_user: UserORM = Depends(require_permission(PERMISSION_MONTHLY_PAYROLL_CALCULATE)),
    service: MonthlyPayrollService = Depends(get_monthly_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> MonthlyPayrollDetailResponse:
    """重新计算钉钉来源的月度核算批次。"""
    detail = service.get_detail(run_id)
    if detail.run.status == "locked":
        raise HTTPException(status_code=400, detail="本月工资已锁定，不能重新计算")
    if detail.run.source_type != "dingtalk":
        raise HTTPException(status_code=400, detail="Excel 来源请重新导入文件后计算")
    detail = await service.calculate_from_dingtalk(
        salary_month=detail.run.salary_month,
        created_by=current_user.username,
        export_excel=True,
    )
    operation_logs.record(
        actor=current_user,
        module="monthly_payroll",
        action="monthly_payroll.recalculate",
        target_type="monthly_payroll_run",
        target_id=str(run_id),
        status="success",
        detail=f"重新计算月度核算 run_id={run_id}",
        **operation_request_meta(http_request),
    )
    return _detail_response(detail)


@router.post("/runs/{run_id}/lock", response_model=MonthlyPayrollRunResponse)
def lock_monthly_run(
    http_request: Request,
    run_id: int,
    current_user: UserORM = Depends(require_permission(PERMISSION_MONTHLY_PAYROLL_LOCK)),
    service: MonthlyPayrollService = Depends(get_monthly_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> MonthlyPayrollRunResponse:
    """锁定月度工资，锁定后不允许重新计算或覆盖。"""
    try:
        run = service.lock_run(run_id, current_user.username)
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    operation_logs.record(
        actor=current_user,
        module="monthly_payroll",
        action="monthly_payroll.lock",
        target_type="monthly_payroll_run",
        target_id=str(run_id),
        status="success",
        detail=f"锁定月度核算 run_id={run_id}",
        **operation_request_meta(http_request),
    )
    return _run_response(run)


@router.get("/runs/{run_id}/export")
def export_monthly_run(
    http_request: Request,
    run_id: int,
    current_user: UserORM = Depends(require_permission(PERMISSION_MONTHLY_PAYROLL_EXPORT)),
    service: MonthlyPayrollService = Depends(get_monthly_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> FileResponse:
    """导出月度工资表。"""
    try:
        path = service.export_path(run_id)
    except (ValueError, FileNotFoundError) as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    operation_logs.record(
        actor=current_user,
        module="monthly_payroll",
        action="monthly_payroll.export",
        target_type="monthly_payroll_run",
        target_id=str(run_id),
        status="success",
        detail=f"导出月度核算 run_id={run_id} path={path}",
        **operation_request_meta(http_request),
    )
    return FileResponse(
        path,
        filename=path.name,
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    )


def _detail_response(detail: MonthlyPayrollDetailData) -> MonthlyPayrollDetailResponse:
    return MonthlyPayrollDetailResponse(
        run=_run_response(detail.run),
        results=[PayrollResultDTO.from_orm_model(result) for result in detail.job.results] if detail.job else [],
        exceptions=[_exception_response(item) for item in detail.exceptions],
    )


def _run_response(run: MonthlyPayrollRunORM) -> MonthlyPayrollRunResponse:
    return MonthlyPayrollRunResponse(
        id=run.id,
        salary_month=run.salary_month,
        source_type=run.source_type,
        source_job_id=run.source_job_id,
        status=run.status,
        employee_count=run.employee_count,
        gross_total=run.gross_total,
        net_total=run.net_total,
        deduction_total=run.deduction_total,
        overtime_total=run.overtime_total,
        locked_by=run.locked_by,
        locked_at=run.locked_at,
        created_by=run.created_by,
        created_at=run.created_at,
        updated_at=run.updated_at,
    )


def _exception_response(item: PayrollExceptionORM) -> PayrollExceptionItem:
    return PayrollExceptionItem(
        id=item.id,
        run_id=item.run_id,
        sync_job_id=item.sync_job_id,
        employee_id=item.employee_id,
        employee_no=item.employee_no,
        employee_name=item.employee_name,
        salary_month=item.salary_month,
        exception_type=item.exception_type,
        severity=item.severity,
        message=item.message,
        status=item.status,
        created_at=item.created_at,
        resolved_at=item.resolved_at,
    )


def _record_failed(
    operation_logs: OperationLogService,
    current_user: UserORM,
    http_request: Request,
    action: str,
    reason: str,
) -> None:
    operation_logs.record(
        actor=current_user,
        module="monthly_payroll",
        action=action,
        target_type="monthly_payroll_run",
        target_id="",
        status="failed",
        detail=reason,
        **operation_request_meta(http_request),
    )
