from __future__ import annotations

from sqlalchemy.orm import Session

from ..core.timezone import now_shanghai
from ..database.orm import MonthlyPayrollRunORM, PayrollExceptionORM


class MonthlyPayrollRepository:
    """月度核算仓库：维护核算批次和异常清单。"""

    def __init__(self, session: Session):
        self.session = session

    def create_run(
        self,
        *,
        salary_month: str,
        source_type: str,
        source_job_id: str,
        created_by: str,
        status: str = "calculated",
    ) -> MonthlyPayrollRunORM:
        existing = self.get_run_by_month(salary_month)
        if existing and existing.status == "locked":
            raise ValueError("本月工资已锁定，不能重新计算")
        if existing is not None:
            existing.source_type = source_type
            existing.source_job_id = source_job_id
            existing.status = status
            existing.created_by = created_by
            existing.updated_at = now_shanghai()
            self.session.commit()
            self.session.refresh(existing)
            return existing

        run = MonthlyPayrollRunORM(
            salary_month=salary_month,
            source_type=source_type,
            source_job_id=source_job_id,
            status=status,
            created_by=created_by,
        )
        self.session.add(run)
        self.session.commit()
        self.session.refresh(run)
        return run

    def get_run(self, run_id: int) -> MonthlyPayrollRunORM | None:
        return self.session.query(MonthlyPayrollRunORM).filter(MonthlyPayrollRunORM.id == run_id).one_or_none()

    def require_run(self, run_id: int) -> MonthlyPayrollRunORM:
        run = self.get_run(run_id)
        if run is None:
            raise ValueError("月度核算批次不存在")
        return run

    def get_run_by_month(self, salary_month: str) -> MonthlyPayrollRunORM | None:
        return (
            self.session.query(MonthlyPayrollRunORM)
            .filter(MonthlyPayrollRunORM.salary_month == salary_month)
            .order_by(MonthlyPayrollRunORM.updated_at.desc(), MonthlyPayrollRunORM.id.desc())
            .first()
        )

    def list_runs(self, salary_month: str | None = None) -> list[MonthlyPayrollRunORM]:
        query = self.session.query(MonthlyPayrollRunORM)
        if salary_month:
            query = query.filter(MonthlyPayrollRunORM.salary_month == salary_month)
        return query.order_by(MonthlyPayrollRunORM.salary_month.desc(), MonthlyPayrollRunORM.id.desc()).all()

    def update_run_totals(
        self,
        run_id: int,
        *,
        employee_count: int,
        gross_total: float,
        net_total: float,
        deduction_total: float,
        overtime_total: float,
        status: str = "calculated",
    ) -> MonthlyPayrollRunORM:
        run = self.require_run(run_id)
        if run.status == "locked":
            raise ValueError("本月工资已锁定，不能重新计算")
        run.employee_count = employee_count
        run.gross_total = gross_total
        run.net_total = net_total
        run.deduction_total = deduction_total
        run.overtime_total = overtime_total
        run.status = status
        run.updated_at = now_shanghai()
        self.session.commit()
        self.session.refresh(run)
        return run

    def lock_run(self, run_id: int, locked_by: str) -> MonthlyPayrollRunORM:
        run = self.require_run(run_id)
        run.status = "locked"
        run.locked_by = locked_by
        run.locked_at = now_shanghai()
        run.updated_at = now_shanghai()
        self.session.commit()
        self.session.refresh(run)
        return run

    def replace_exceptions(self, run_id: int, rows: list[PayrollExceptionORM]) -> None:
        self.session.query(PayrollExceptionORM).filter(PayrollExceptionORM.run_id == run_id).delete(
            synchronize_session=False
        )
        if rows:
            self.session.add_all(rows)
        self.session.commit()

    def list_exceptions(self, run_id: int) -> list[PayrollExceptionORM]:
        return (
            self.session.query(PayrollExceptionORM)
            .filter(PayrollExceptionORM.run_id == run_id)
            .order_by(PayrollExceptionORM.severity.desc(), PayrollExceptionORM.id.asc())
            .all()
        )
