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_PAYROLL_DINGTALK_CALCULATE,
    PERMISSION_PAYROLL_EXCEL_CALCULATE,
    PERMISSION_PAYROLL_FILE_DOWNLOAD,
    PERMISSION_PAYROLL_JOB_VIEW,
)
from ...database.orm import UserORM
from ...integrations.dingtalk import DingTalkError
from ...services import OperationLogService, PayrollApplicationService
from ..dependencies import get_operation_log_service, get_payroll_service, require_permission
from ..request_meta import operation_request_meta
from ..schemas.payroll import (
    DingTalkPayrollRequest,
    DingTalkPayrollResponse,
    ExcelPayrollResponse,
    PayrollJobResponse,
)

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


@router.post(
    "/excel",
    response_model=ExcelPayrollResponse,
)
def calculate_from_excel(
    http_request: Request,
    file: UploadFile = File(...),
    config_path: str | None = Form(default=None),
    export_excel: bool = Form(default=True),
    current_user: UserORM = Depends(require_permission(PERMISSION_PAYROLL_EXCEL_CALCULATE)),
    service: PayrollApplicationService = Depends(get_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> ExcelPayrollResponse:
    """上传月度汇总表并立即计算，计算任务和员工结果都会落库。"""
    try:
        computation = service.calculate_from_excel_upload(
            filename=file.filename or "attendance.xlsx",
            stream=file.file,
            config_path=config_path,
            export_excel=export_excel,
        )
    except ValueError as exc:
        operation_logs.record(
            actor=current_user,
            module="payroll",
            action="payroll.excel.calculate",
            target_type="payroll_job",
            target_id="",
            status="failed",
            detail=f"Excel工资计算失败 filename={file.filename} reason={exc}",
            **operation_request_meta(http_request),
        )
        logger.error("Excel 工资计算接口失败 filename=%s reason=%s", file.filename, exc)
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except Exception as exc:
        operation_logs.record(
            actor=current_user,
            module="payroll",
            action="payroll.excel.calculate",
            target_type="payroll_job",
            target_id="",
            status="failed",
            detail=f"Excel工资计算异常 filename={file.filename} reason={exc}",
            **operation_request_meta(http_request),
        )
        raise
    operation_logs.record(
        actor=current_user,
        module="payroll",
        action="payroll.excel.calculate",
        target_type="payroll_job",
        target_id=computation.job_id,
        status="success",
        detail=f"Excel工资计算成功 filename={file.filename} employee_count={computation.employee_count}",
        **operation_request_meta(http_request),
    )
    return ExcelPayrollResponse.from_computation(computation)


@router.post(
    "/dingtalk/realtime",
    response_model=DingTalkPayrollResponse,
)
async def calculate_from_dingtalk(
    http_request: Request,
    request: DingTalkPayrollRequest,
    current_user: UserORM = Depends(require_permission(PERMISSION_PAYROLL_DINGTALK_CALCULATE)),
    service: PayrollApplicationService = Depends(get_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> DingTalkPayrollResponse:
    """从钉钉实时拉打卡记录，再复用同一套工资计算规则。"""
    try:
        computation = await service.calculate_from_dingtalk_realtime(
            user_ids=request.user_ids,
            start_date=request.start_date,
            end_date=request.end_date,
            config_path=request.config_path,
            export_excel=request.export_excel,
        )
    except ValueError as exc:
        operation_logs.record(
            actor=current_user,
            module="payroll",
            action="payroll.dingtalk.calculate",
            target_type="payroll_job",
            target_id="",
            status="failed",
            detail=f"钉钉工资计算参数错误 user_count={len(request.user_ids)} reason={exc}",
            **operation_request_meta(http_request),
        )
        logger.error("钉钉工资计算接口参数错误 user_count=%s reason=%s", len(request.user_ids), exc)
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except DingTalkError as exc:
        operation_logs.record(
            actor=current_user,
            module="payroll",
            action="payroll.dingtalk.calculate",
            target_type="payroll_job",
            target_id="",
            status="failed",
            detail=f"钉钉工资计算调用失败 user_count={len(request.user_ids)} reason={exc}",
            **operation_request_meta(http_request),
        )
        logger.error("钉钉工资计算接口调用失败 user_count=%s reason=%s", len(request.user_ids), exc)
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    except Exception as exc:
        operation_logs.record(
            actor=current_user,
            module="payroll",
            action="payroll.dingtalk.calculate",
            target_type="payroll_job",
            target_id="",
            status="failed",
            detail=f"钉钉工资计算异常 user_count={len(request.user_ids)} reason={exc}",
            **operation_request_meta(http_request),
        )
        raise
    operation_logs.record(
        actor=current_user,
        module="payroll",
        action="payroll.dingtalk.calculate",
        target_type="payroll_job",
        target_id=computation.job_id,
        status="success",
        detail=f"钉钉工资计算成功 user_count={len(request.user_ids)} employee_count={computation.employee_count}",
        **operation_request_meta(http_request),
    )
    return DingTalkPayrollResponse.from_computation(computation)


@router.get(
    "/jobs/{job_id}",
    response_model=PayrollJobResponse,
)
def get_payroll_job(
    http_request: Request,
    job_id: str,
    current_user: UserORM = Depends(require_permission(PERMISSION_PAYROLL_JOB_VIEW)),
    service: PayrollApplicationService = Depends(get_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> PayrollJobResponse:
    """查看已保存的计算任务及员工级结果。"""
    job = service.get_job(job_id)
    if job is None:
        operation_logs.record(
            actor=current_user,
            module="payroll",
            action="payroll.job.view",
            target_type="payroll_job",
            target_id=job_id,
            status="failed",
            detail="计算任务不存在",
            **operation_request_meta(http_request),
        )
        logger.error("查询工资计算任务失败 job_id=%s reason=not_found", job_id)
        raise HTTPException(status_code=404, detail="计算任务不存在")
    operation_logs.record(
        actor=current_user,
        module="payroll",
        action="payroll.job.view",
        target_type="payroll_job",
        target_id=job_id,
        status="success",
        detail=f"查询工资计算任务成功 status={job.status} employee_count={job.employee_count}",
        **operation_request_meta(http_request),
    )
    return PayrollJobResponse.from_orm_model(job)


@router.get(
    "/download/{filename}",
)
def download_payroll_file(
    http_request: Request,
    filename: str,
    current_user: UserORM = Depends(require_permission(PERMISSION_PAYROLL_FILE_DOWNLOAD)),
    service: PayrollApplicationService = Depends(get_payroll_service),
    operation_logs: OperationLogService = Depends(get_operation_log_service),
) -> FileResponse:
    """下载计算时导出的 Excel 结果文件。"""
    try:
        path = service.resolve_output_file(filename)
    except FileNotFoundError as exc:
        operation_logs.record(
            actor=current_user,
            module="payroll",
            action="payroll.file.download",
            target_type="file",
            target_id=filename,
            status="failed",
            detail=str(exc),
            **operation_request_meta(http_request),
        )
        logger.error("下载工资结果失败 filename=%s reason=%s", filename, exc)
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    operation_logs.record(
        actor=current_user,
        module="payroll",
        action="payroll.file.download",
        target_type="file",
        target_id=filename,
        status="success",
        detail=f"下载工资结果成功 path={path}",
        **operation_request_meta(http_request),
    )
    logger.info("下载工资结果成功 filename=%s path=%s", filename, path)
    return FileResponse(
        path,
        filename=path.name,
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    )
