from __future__ import annotations

from uuid import uuid4

from sqlalchemy.orm import Session, selectinload

from ..database.orm import (
    AttendanceRecordORM,
    EmployeeORM,
    LeaveRecordORM,
    OvertimeRecordORM,
    PayrollJobORM,
    PayrollResultORM,
    SalaryDetailORM,
    SalaryRecordORM,
)
from ..domain.models import PayrollResult


class PayrollRepository:
    """工资任务仓库：只负责 Payroll ORM 的读写，不包含业务计算。"""

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

    def create_job(self, source_type: str, input_file: str | None = None) -> PayrollJobORM:
        job = PayrollJobORM(
            id=uuid4().hex,
            source_type=source_type,
            status="running",
            input_file=input_file,
        )
        self.session.add(job)
        self.session.commit()
        self.session.refresh(job)
        return job

    def save_results(self, job_id: str, results: list[PayrollResult]) -> None:
        """批量保存员工结果，保持计算服务和 ORM 字段映射隔离。"""
        job = self.require_job(job_id)
        rows = [self._result_row(job_id, result) for result in results]
        normalized_rows = []
        salary_rows = []
        for result in results:
            employee = self._match_employee(result)
            normalized_rows.extend(self._attendance_rows(job.id, job.source_type, result, employee))
            normalized_rows.extend(self._leave_rows(job.id, job.source_type, result, employee))
            normalized_rows.extend(self._overtime_rows(job.id, job.source_type, result, employee))
            salary_rows.append(self._salary_record_row(job_id, result, employee))
        self.session.add_all([*rows, *normalized_rows, *salary_rows])
        self.session.commit()

    def mark_completed(
        self,
        job_id: str,
        employee_count: int,
        output_file: str | None = None,
    ) -> PayrollJobORM:
        job = self.require_job(job_id)
        job.status = "completed"
        job.employee_count = employee_count
        job.output_file = output_file
        self.session.commit()
        self.session.refresh(job)
        return job

    def mark_failed(self, job_id: str, error_message: str) -> PayrollJobORM:
        job = self.require_job(job_id)
        job.status = "failed"
        job.error_message = error_message
        self.session.commit()
        self.session.refresh(job)
        return job

    def get_job(self, job_id: str) -> PayrollJobORM | None:
        return (
            self.session.query(PayrollJobORM)
            .options(selectinload(PayrollJobORM.results))
            .filter(PayrollJobORM.id == job_id)
            .one_or_none()
        )

    def require_job(self, job_id: str) -> PayrollJobORM:
        job = self.get_job(job_id)
        if job is None:
            raise KeyError(f"Payroll job not found: {job_id}")
        return job

    def _result_row(self, job_id: str, result: PayrollResult) -> PayrollResultORM:
        employee = result.employee
        return PayrollResultORM(
            job_id=job_id,
            name=employee.name,
            department=employee.department,
            position=employee.position,
            attendance_group=employee.attendance_group,
            salary_month=result.salary_month,
            salary_mode=result.salary_mode,
            attendance_days=result.attendance_days,
            absence_days=result.absence_days,
            actual_work_hours=result.actual_work_hours,
            punch_overtime_hours=result.punch_overtime_hours,
            leave_hours=result.leave_hours,
            remaining_overtime_hours=result.remaining_overtime_hours,
            workday_overtime_hours=result.workday_overtime_hours,
            weekend_overtime_hours=result.weekend_overtime_hours,
            holiday_overtime_hours=result.holiday_overtime_hours,
            minor_late_count=result.minor_late_count,
            major_late_count=result.major_late_count,
            penalized_late_count=result.penalized_late_count,
            late_deduction=result.late_deduction,
            missing_card_count=result.missing_card_count,
            missing_card_deduction=result.missing_card_deduction,
            total_deduction=result.total_deduction,
            base_salary=result.base_salary,
            base_pay=result.base_pay,
            commission_amount=result.commission_amount,
            overtime_rate=result.overtime_rate,
            weekend_overtime_rate=result.weekend_overtime_rate,
            holiday_overtime_rate=result.holiday_overtime_rate,
            overtime_pay=result.overtime_pay,
            gross_salary=result.gross_salary,
            net_salary=result.net_salary,
            note=result.note,
        )

    def _match_employee(self, result: PayrollResult) -> EmployeeORM | None:
        employee = result.employee
        query = self.session.query(EmployeeORM)
        if employee.employee_no:
            matched = query.filter(EmployeeORM.employee_no == employee.employee_no).one_or_none()
            if matched is not None:
                return matched
        return query.filter(EmployeeORM.name == employee.name).one_or_none()

    def _attendance_rows(
        self,
        job_id: str,
        source_type: str,
        result: PayrollResult,
        employee: EmployeeORM | None,
    ) -> list[AttendanceRecordORM]:
        rows: list[AttendanceRecordORM] = []
        for record in result.employee.daily_records:
            rows.append(
                AttendanceRecordORM(
                    job_id=job_id,
                    employee_id=employee.id if employee else None,
                    employee_no=result.employee.employee_no,
                    employee_name=result.employee.name,
                    department=result.employee.department,
                    work_date=record.work_date,
                    first_punch=_time_text(record.first_punch),
                    last_punch=_time_text(record.last_punch),
                    attendance_status=_attendance_status(record),
                    late_minutes=sum(record.late_minutes),
                    missing_card_count=record.missing_card_count,
                    leave_hours=record.leave_hours_in_cell,
                    overtime_hours=record.punch_overtime_hours,
                    raw_text=record.raw_text,
                    source_type=source_type,
                )
            )
        return rows

    def _leave_rows(
        self,
        job_id: str,
        source_type: str,
        result: PayrollResult,
        employee: EmployeeORM | None,
    ) -> list[LeaveRecordORM]:
        return [
            LeaveRecordORM(
                job_id=job_id,
                employee_id=employee.id if employee else None,
                employee_no=result.employee.employee_no,
                employee_name=result.employee.name,
                leave_type=event.kind,
                start_text=event.start_text,
                end_text=event.end_text,
                hours=event.hours,
                source_type=source_type,
                status="approved",
            )
            for event in result.employee.unique_leave_events
        ]

    def _overtime_rows(
        self,
        job_id: str,
        source_type: str,
        result: PayrollResult,
        employee: EmployeeORM | None,
    ) -> list[OvertimeRecordORM]:
        rows: list[OvertimeRecordORM] = []
        for record in result.employee.daily_records:
            if record.punch_overtime_hours <= 0:
                continue
            overtime_type = _overtime_type(record.work_date)
            rate = result.weekend_overtime_rate if overtime_type == "weekend" else result.overtime_rate
            rows.append(
                OvertimeRecordORM(
                    job_id=job_id,
                    employee_id=employee.id if employee else None,
                    employee_no=result.employee.employee_no,
                    employee_name=result.employee.name,
                    work_date=record.work_date,
                    overtime_type=overtime_type,
                    hours=record.punch_overtime_hours,
                    rate=rate,
                    amount=round(record.punch_overtime_hours * rate, 2),
                    source_type=source_type,
                )
            )
        return rows

    def _salary_record_row(
        self,
        job_id: str,
        result: PayrollResult,
        employee: EmployeeORM | None,
    ) -> SalaryRecordORM:
        record = SalaryRecordORM(
            job_id=job_id,
            employee_id=employee.id if employee else None,
            employee_no=result.employee.employee_no,
            employee_name=result.employee.name,
            department=result.employee.department,
            salary_month=result.salary_month,
            salary_mode=result.salary_mode,
            base_pay=result.base_pay or 0,
            commission_amount=result.commission_amount,
            overtime_pay=result.overtime_pay,
            deduction_amount=result.total_deduction,
            gross_salary=result.gross_salary or 0,
            net_salary=result.net_salary or 0,
            remark=result.note,
        )
        record.details = [
            SalaryDetailORM(item_type="earning", item_name="基础工资", amount=result.base_pay or 0),
            SalaryDetailORM(item_type="earning", item_name="提成/奖金", amount=result.commission_amount),
            SalaryDetailORM(
                item_type="earning",
                item_name="工作日加班费",
                amount=round(result.workday_overtime_hours * result.overtime_rate, 2),
                quantity=result.workday_overtime_hours,
                unit_price=result.overtime_rate,
            ),
            SalaryDetailORM(
                item_type="earning",
                item_name="周末加班费",
                amount=round(result.weekend_overtime_hours * result.weekend_overtime_rate, 2),
                quantity=result.weekend_overtime_hours,
                unit_price=result.weekend_overtime_rate,
            ),
            SalaryDetailORM(
                item_type="earning",
                item_name="节假日加班费",
                amount=round(result.holiday_overtime_hours * result.holiday_overtime_rate, 2),
                quantity=result.holiday_overtime_hours,
                unit_price=result.holiday_overtime_rate,
            ),
            SalaryDetailORM(
                item_type="deduction",
                item_name="迟到扣款",
                amount=result.late_deduction,
                quantity=result.penalized_late_count,
            ),
            SalaryDetailORM(
                item_type="deduction",
                item_name="缺卡扣款",
                amount=result.missing_card_deduction,
                quantity=result.missing_card_count,
            ),
            SalaryDetailORM(item_type="stat", item_name="出勤天数", amount=0, quantity=result.attendance_days),
            SalaryDetailORM(item_type="stat", item_name="请假工时", amount=0, quantity=result.leave_hours),
            SalaryDetailORM(item_type="stat", item_name="剩余加班工时", amount=0, quantity=result.remaining_overtime_hours),
        ]
        return record


def _time_text(value) -> str:
    if value is None:
        return ""
    return value.strftime("%H:%M")


def _attendance_status(record) -> str:
    if record.first_punch or record.last_punch:
        if record.missing_card_count:
            return "missing"
        return "present"
    if record.leave_hours_in_cell:
        return "leave"
    return "absent"


def _overtime_type(work_date) -> str:
    return "weekend" if work_date.weekday() >= 5 else "workday"
