from __future__ import annotations

from datetime import datetime, time

from ..core.config import AppConfig
from .models import DailyAttendance, EmployeeAttendance, PayrollResult


class PayrollCalculator:
    """工资规则计算器，不处理 Excel、HTTP、数据库等外部细节。"""

    def __init__(self, config: AppConfig):
        self.config = config
        self.off_work_time = datetime.strptime(config.attendance.off_work_time, "%H:%M").time()

    def calculate(self, employees: list[EmployeeAttendance]) -> list[PayrollResult]:
        """先按每日下班打卡推算加班，再按员工聚合扣款和工资。"""
        for employee in employees:
            for record in employee.daily_records:
                record.punch_overtime_hours = self._overtime_from_checkout(record)
        return [self._calculate_employee(employee) for employee in employees]

    def _calculate_employee(self, employee: EmployeeAttendance) -> PayrollResult:
        punch_overtime_hours = round(sum(r.punch_overtime_hours for r in employee.daily_records), 2)
        leave_hours = round(sum(event.hours for event in employee.unique_leave_events), 2)
        remaining_overtime_hours = round(punch_overtime_hours - leave_hours, 2)
        salary_month = _salary_month(employee)
        attendance_days = round(
            _number(employee.summary_fields.get("出勤天数")) or _count_attendance_days(employee),
            2,
        )
        expected_work_days = _expected_work_days(employee, self.config.attendance.weekend_days)
        absence_days = round(
            _number(employee.summary_fields.get("缺勤天数")) or max(0.0, expected_work_days - attendance_days),
            2,
        )
        actual_work_hours = round(attendance_days * self.config.attendance.standard_daily_hours, 2)
        raw_workday_hours, raw_weekend_hours, raw_holiday_hours = self._split_overtime_hours(employee)
        workday_overtime_hours, weekend_overtime_hours, holiday_overtime_hours = _allocate_payable_overtime(
            raw_workday_hours,
            raw_weekend_hours,
            raw_holiday_hours,
            leave_hours,
        )

        # 5 分钟内迟到先累计免扣次数，超过免扣次数后按次扣款。
        late_minutes = [minutes for record in employee.daily_records for minutes in record.late_minutes]
        minor_late_count = sum(1 for minutes in late_minutes if minutes <= self.config.attendance.minor_late_minutes)
        major_late_count = sum(1 for minutes in late_minutes if minutes > self.config.attendance.minor_late_minutes)
        penalized_minor_count = max(0, minor_late_count - self.config.attendance.minor_late_free_times)
        penalized_late_count = penalized_minor_count + major_late_count
        late_deduction = round(penalized_late_count * self.config.attendance.late_penalty, 2)

        daily_missing_count = sum(record.missing_card_count for record in employee.daily_records)
        summary_missing_count = int(
            _number(employee.summary_fields.get("上班缺卡次数"))
            + _number(employee.summary_fields.get("下班缺卡次数"))
        )
        # 汇总列和每日明细都可能记录缺卡，取较大值避免漏扣。
        missing_card_count = max(daily_missing_count, summary_missing_count)
        missing_card_deduction = round(
            missing_card_count * self.config.attendance.missing_card_penalty, 2
        )
        total_deduction = round(late_deduction + missing_card_deduction, 2)

        pay_config = self.config.payroll.employees.get(employee.name)
        salary_mode = pay_config.salary_mode if pay_config else "monthly"
        base_salary = pay_config.base_salary if pay_config else None
        base_pay = _base_pay(pay_config, actual_work_hours)
        overtime_rate = (
            pay_config.overtime_rate
            if pay_config and pay_config.overtime_rate is not None
            else self.config.payroll.default_overtime_rate
        )
        weekend_overtime_rate = (
            pay_config.weekend_overtime_rate
            if pay_config and pay_config.weekend_overtime_rate is not None
            else self.config.payroll.default_weekend_overtime_rate
        )
        holiday_overtime_rate = (
            pay_config.holiday_overtime_rate
            if pay_config and pay_config.holiday_overtime_rate is not None
            else self.config.payroll.default_holiday_overtime_rate
        )
        overtime_pay = round(
            workday_overtime_hours * overtime_rate
            + weekend_overtime_hours * weekend_overtime_rate
            + holiday_overtime_hours * holiday_overtime_rate,
            2,
        )
        commission_amount = round(pay_config.commission_amount, 2) if pay_config else 0.0

        gross_salary = None
        net_salary = None
        note = ""
        if base_pay is None:
            note = "未配置薪资档案，已计算考勤扣款和剩余加班工时"
        else:
            gross_salary = round(base_pay + commission_amount + overtime_pay, 2)
            net_salary = round(gross_salary - total_deduction, 2)

        return PayrollResult(
            employee=employee,
            salary_month=salary_month,
            salary_mode=salary_mode,
            attendance_days=attendance_days,
            absence_days=absence_days,
            actual_work_hours=actual_work_hours,
            punch_overtime_hours=punch_overtime_hours,
            leave_hours=leave_hours,
            remaining_overtime_hours=remaining_overtime_hours,
            workday_overtime_hours=workday_overtime_hours,
            weekend_overtime_hours=weekend_overtime_hours,
            holiday_overtime_hours=holiday_overtime_hours,
            minor_late_count=minor_late_count,
            major_late_count=major_late_count,
            penalized_late_count=penalized_late_count,
            late_deduction=late_deduction,
            missing_card_count=missing_card_count,
            missing_card_deduction=missing_card_deduction,
            total_deduction=total_deduction,
            base_salary=base_salary,
            base_pay=base_pay,
            commission_amount=commission_amount,
            overtime_rate=overtime_rate,
            weekend_overtime_rate=weekend_overtime_rate,
            holiday_overtime_rate=holiday_overtime_rate,
            overtime_pay=overtime_pay,
            gross_salary=gross_salary,
            net_salary=net_salary,
            note=note,
        )

    def _split_overtime_hours(self, employee: EmployeeAttendance) -> tuple[float, float, float]:
        workday_hours = 0.0
        weekend_hours = 0.0
        holiday_hours = 0.0
        holidays = set(self.config.attendance.legal_holidays)
        weekend_days = set(self.config.attendance.weekend_days)
        for record in employee.daily_records:
            if record.work_date.isoformat() in holidays:
                holiday_hours += record.punch_overtime_hours
            elif record.work_date.weekday() in weekend_days:
                weekend_hours += record.punch_overtime_hours
            else:
                workday_hours += record.punch_overtime_hours
        return round(workday_hours, 2), round(weekend_hours, 2), round(holiday_hours, 2)

    def _overtime_from_checkout(self, record: DailyAttendance) -> float:
        """按下班打卡时间向下取整计算加班，例如 20:30 -> 3 小时。"""
        checkout = record.last_punch
        if checkout is None or not _is_after(checkout, self.off_work_time):
            return 0.0

        checkout_minutes = checkout.hour * 60 + checkout.minute
        off_work_minutes = self.off_work_time.hour * 60 + self.off_work_time.minute
        overtime_minutes = checkout_minutes - off_work_minutes
        round_minutes = self.config.attendance.overtime_round_minutes
        return (overtime_minutes // round_minutes) * round_minutes / 60


def _is_after(left: time, right: time) -> bool:
    return (left.hour, left.minute) > (right.hour, right.minute)


def _number(value: object) -> float:
    if value in (None, ""):
        return 0.0
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.0


def _salary_month(employee: EmployeeAttendance) -> str:
    if not employee.daily_records:
        return ""
    return employee.daily_records[0].work_date.strftime("%Y-%m")


def _count_attendance_days(employee: EmployeeAttendance) -> float:
    return float(
        sum(1 for record in employee.daily_records if record.first_punch is not None or record.last_punch is not None)
    )


def _expected_work_days(employee: EmployeeAttendance, weekend_days: tuple[int, ...]) -> float:
    configured = _number(employee.summary_fields.get("应出勤天数"))
    if configured:
        return configured
    weekends = set(weekend_days)
    return float(sum(1 for record in employee.daily_records if record.work_date.weekday() not in weekends))


def _allocate_payable_overtime(
    workday_hours: float,
    weekend_hours: float,
    holiday_hours: float,
    leave_hours: float,
) -> tuple[float, float, float]:
    """请假工时先冲抵工作日加班，再冲抵周末和节假日加班。"""
    remaining_leave = max(0.0, leave_hours)
    workday_payable = max(0.0, workday_hours - remaining_leave)
    remaining_leave = max(0.0, remaining_leave - workday_hours)
    weekend_payable = max(0.0, weekend_hours - remaining_leave)
    remaining_leave = max(0.0, remaining_leave - weekend_hours)
    holiday_payable = max(0.0, holiday_hours - remaining_leave)
    return round(workday_payable, 2), round(weekend_payable, 2), round(holiday_payable, 2)


def _base_pay(pay_config, actual_work_hours: float) -> float | None:
    if pay_config is None:
        return None
    mode = pay_config.salary_mode
    if mode == "hourly":
        return round(pay_config.hourly_rate * actual_work_hours, 2)
    if mode == "piecework":
        return round(pay_config.piece_quantity * pay_config.piece_unit_price, 2)
    if mode == "probation":
        if pay_config.probation_type == "fixed" and pay_config.probation_salary is not None:
            return round(pay_config.probation_salary, 2)
        if pay_config.base_salary is None:
            return None
        return round(pay_config.base_salary * pay_config.probation_ratio, 2)
    if pay_config.base_salary is None:
        return None
    return round(pay_config.base_salary, 2)
