from __future__ import annotations

import re
from datetime import date, datetime, time, timedelta
from pathlib import Path
from typing import Iterable

from openpyxl import load_workbook

from ..core.config import AttendanceRules
from ..domain.models import DailyAttendance, EmployeeAttendance, LeaveEvent

PUNCH_LINE_RE = re.compile(r"\((?P<punches>[^()]*)\)\s*$", re.S)
TIME_RE = re.compile(r"^\d{1,2}:\d{2}$")
LATE_RE = re.compile(r"迟到(?P<minutes>\d+(?:\.\d+)?)分钟")
PERIOD_RE = re.compile(r"统计日期：(?P<start>\d{4}-\d{2}-\d{2})\s+至\s+(?P<end>\d{4}-\d{2}-\d{2})")

BASE_COLUMNS = ("姓名", "考勤组", "部门", "工号", "职位", "UserId")


class MonthlySummaryParser:
    """解析钉钉月度汇总 Excel，输出统一的员工考勤领域模型。"""

    def __init__(self, rules: AttendanceRules):
        self.rules = rules
        self.leave_re = re.compile(
            rf"(?P<kind>{'|'.join(map(re.escape, rules.leave_keywords))})"
            r"(?P<start>\d{2}-\d{2}\s+\d{2}:\d{2})到"
            r"(?P<end>\d{2}-\d{2}\s+\d{2}:\d{2})\s+"
            r"(?P<hours>\d+(?:\.\d+)?)小时"
        )

    def parse(self, workbook_path: str | Path) -> list[EmployeeAttendance]:
        workbook = load_workbook(workbook_path, data_only=True)
        sheet = workbook.active
        start_date, _ = self._period(sheet["A1"].value)
        header_row = 3
        data_start_row = 5

        daily_start_col = self._find_daily_start_col(sheet, header_row)
        header_map = self._header_map(sheet, header_row, daily_start_col)
        employees: list[EmployeeAttendance] = []

        for row_idx in range(data_start_row, sheet.max_row + 1):
            name = self._cell_text(sheet.cell(row_idx, header_map["姓名"]).value)
            if not name:
                continue

            employee = EmployeeAttendance(
                name=name,
                attendance_group=self._value_by_header(sheet, row_idx, header_map, "考勤组"),
                department=self._value_by_header(sheet, row_idx, header_map, "部门"),
                employee_no=self._value_by_header(sheet, row_idx, header_map, "工号"),
                position=self._value_by_header(sheet, row_idx, header_map, "职位"),
                user_id=self._value_by_header(sheet, row_idx, header_map, "UserId"),
                summary_fields=self._summary_fields(sheet, row_idx, header_map),
            )

            for col_idx in range(daily_start_col, sheet.max_column + 1):
                work_date = start_date + timedelta(days=col_idx - daily_start_col)
                raw_text = self._cell_text(sheet.cell(row_idx, col_idx).value)
                record = self._parse_daily_record(work_date, raw_text)
                employee.daily_records.append(record)

            employees.append(employee)

        return employees

    def _parse_daily_record(self, work_date: date, raw_text: str) -> DailyAttendance:
        punch_times = self._parse_punch_times(raw_text)
        record = DailyAttendance(
            work_date=work_date,
            raw_text=raw_text,
            first_punch=punch_times[0] if punch_times else None,
            last_punch=punch_times[-1] if punch_times else None,
            late_minutes=self._parse_late_minutes(raw_text),
            missing_card_count=self._parse_missing_cards(raw_text),
            leave_events=self._parse_leave_events(raw_text),
        )
        return record

    def _parse_punch_times(self, raw_text: str) -> list[time]:
        """每日单元格最后一行形如 (08:30,17:00)，这里只取真实打卡时间。"""
        match = PUNCH_LINE_RE.search(raw_text or "")
        if not match:
            return []
        result: list[time] = []
        for item in match.group("punches").split(","):
            text = item.strip()
            if not TIME_RE.match(text):
                continue
            result.append(datetime.strptime(text, "%H:%M").time())
        return result

    def _parse_late_minutes(self, raw_text: str) -> list[int]:
        return [int(float(match.group("minutes"))) for match in LATE_RE.finditer(raw_text or "")]

    def _parse_missing_cards(self, raw_text: str) -> int:
        return (raw_text or "").count("缺卡")

    def _parse_leave_events(self, raw_text: str) -> list[LeaveEvent]:
        """提取请假/调休事件；跨天申请后续会按事件 key 去重。"""
        return [
            LeaveEvent(
                kind=match.group("kind"),
                start_text=match.group("start"),
                end_text=match.group("end"),
                hours=float(match.group("hours")),
            )
            for match in self.leave_re.finditer(raw_text or "")
        ]

    def _period(self, title: object) -> tuple[date, date]:
        match = PERIOD_RE.search(self._cell_text(title))
        if not match:
            raise ValueError("未能从 A1 识别统计日期，期望格式：统计日期：YYYY-MM-DD 至 YYYY-MM-DD")
        return (
            datetime.strptime(match.group("start"), "%Y-%m-%d").date(),
            datetime.strptime(match.group("end"), "%Y-%m-%d").date(),
        )

    def _find_daily_start_col(self, sheet, header_row: int) -> int:
        for col_idx in range(1, sheet.max_column + 1):
            if self._cell_text(sheet.cell(header_row, col_idx).value) == "考勤结果":
                return col_idx
        raise ValueError("未找到“考勤结果”列，无法识别每日考勤明细。")

    def _header_map(self, sheet, header_row: int, stop_col: int) -> dict[str, int]:
        mapping: dict[str, int] = {}
        for col_idx in range(1, stop_col):
            header = self._cell_text(sheet.cell(header_row, col_idx).value)
            if header:
                mapping[header] = col_idx
        missing = [header for header in BASE_COLUMNS if header not in mapping]
        if missing:
            raise ValueError(f"缺少必要表头：{', '.join(missing)}")
        return mapping

    def _summary_fields(self, sheet, row_idx: int, header_map: dict[str, int]) -> dict[str, float | str]:
        fields: dict[str, float | str] = {}
        for header, col_idx in header_map.items():
            if header in BASE_COLUMNS:
                continue
            value = sheet.cell(row_idx, col_idx).value
            fields[header] = self._normalize_value(value)
        return fields

    def _value_by_header(self, sheet, row_idx: int, header_map: dict[str, int], header: str) -> str:
        return self._cell_text(sheet.cell(row_idx, header_map[header]).value)

    def _normalize_value(self, value: object) -> float | str:
        text = self._cell_text(value)
        if text == "":
            return 0.0
        try:
            return float(text)
        except ValueError:
            return text

    def _cell_text(self, value: object) -> str:
        if value is None:
            return ""
        return str(value).strip()


def iter_leave_event_descriptions(events: Iterable[LeaveEvent]) -> Iterable[str]:
    for event in events:
        yield f"{event.kind}{event.start_text}到{event.end_text} {event.hours:g}小时"
