#!/usr/bin/env python3
"""
考勤排班查询导出脚本

[AI Agent 强制门禁] 本脚本执行前必须先阅读：
   references/attendance-schedule.md

职责：
  1. 分批查询排班记录（支持大量用户自动分批）
  2. 将 classId 转为班次名称
  3. 将 userId 转为员工姓名
  4. 输出日历表格式的排班表 Excel（行=员工，列=日期，单元格=班次名称）

用法:
    python attendance_schedule_export.py \
        --users userId1,userId2,userId3 \
        --start 2026-05-19 --end 2026-05-23

    python attendance_schedule_export.py \
        --users userId1,userId2 \
        --start 2026-05-01 --end 2026-05-31 \
        --output my_schedule.xlsx
"""

from __future__ import annotations

import argparse
import os
import sys
from datetime import datetime, timedelta
from typing import Any

from attendance_report_common import (
    DATE_FMT,
    DATETIME_FMT,
    DwsCallError,
    chunk_users,
    error,
    extract_records,
    log,
    parse_datetime_arg,
    resolve_user_names,
    run_dws,
    warn,
    write_excel,
)

# schedule get 接口每批最多用户数（保守值，避免超时）
SCHEDULE_BATCH_SIZE = 20

WEEKDAY_NAMES = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]


# ─────────────────────────────────────────────────────────────────────────────
# 排班数据查询（分批）
# ─────────────────────────────────────────────────────────────────────────────

def fetch_schedule_batch(
    user_ids: list[str],
    start_date: str,
    end_date: str,
) -> list[dict]:
    """调用 dws attendance schedule get 查询一批用户的排班记录。"""
    users_str = ",".join(user_ids)
    try:
        result = run_dws([
            "attendance", "schedule", "get",
            "--users", users_str,
            "--start", start_date,
            "--end", end_date,
        ])
    except DwsCallError as exc:
        error(f"查询排班失败 (users={len(user_ids)}, {start_date}~{end_date}): {exc}")
        return []
    return extract_records(result) if result else []


def fetch_all_schedules(
    user_ids: list[str],
    start_date: str,
    end_date: str,
) -> list[dict]:
    """分批查询所有用户的排班记录，自动处理用户数超限。"""
    all_records: list[dict] = []
    batches = chunk_users(user_ids, SCHEDULE_BATCH_SIZE)
    total = len(batches)

    log(f"📋 共 {len(user_ids)} 人，分 {total} 批查询排班 ({start_date} ~ {end_date})")

    for idx, batch in enumerate(batches, start=1):
        if total > 1:
            log(f"   批次 {idx}/{total}: {len(batch)} 人")
        records = fetch_schedule_batch(batch, start_date, end_date)
        all_records.extend(records)

    log(f"✅ 查询完成，共 {len(all_records)} 条排班记录")
    return all_records


# ─────────────────────────────────────────────────────────────────────────────
# 班次名称映射
# ─────────────────────────────────────────────────────────────────────────────

def build_class_name_map(records: list[dict]) -> dict[int, str]:
    """从排班记录中提取 classId → className 映射。

    优先使用记录自带的 className；缺失时回退 class search 补全。
    """
    class_map: dict[int, str] = {}
    missing_ids: set[int] = set()

    for record in records:
        raw_id = record.get("classId") or record.get("class_id")
        raw_name = record.get("className") or record.get("class_name")
        if raw_id is None:
            continue
        cid = int(raw_id)
        if raw_name and str(raw_name).strip():
            class_map[cid] = str(raw_name).strip()
        elif cid != 0 and cid not in class_map:
            missing_ids.add(cid)

    if missing_ids:
        log(f"🔍 {len(missing_ids)} 个班次缺名称，从 class search 补全 ...")
        try:
            result = run_dws(["attendance", "class", "search", "--page-size", "200"])
            for cls in (extract_records(result) if result else []):
                cid_raw = cls.get("id") or cls.get("classId")
                cname = cls.get("name") or cls.get("className")
                if cid_raw is not None and cname:
                    class_map[int(cid_raw)] = str(cname).strip()
        except DwsCallError as exc:
            warn(f"class search 失败，部分班次将显示为 ID: {exc}")

    return class_map


# ─────────────────────────────────────────────────────────────────────────────
# 日期工具
# ─────────────────────────────────────────────────────────────────────────────

def normalize_work_date(raw: Any) -> str:
    """将排班记录中的 workDate 标准化为 YYYY-MM-DD。"""
    if raw is None:
        return ""
    if isinstance(raw, (int, float)):
        ts = raw / 1000 if raw > 1e12 else raw
        try:
            return datetime.fromtimestamp(ts).strftime(DATE_FMT)
        except (OSError, ValueError, OverflowError):
            return ""
    s = str(raw).strip()
    if len(s) >= 10 and s[4] == "-" and s[7] == "-":
        return s[:10]
    return s


def generate_date_range(start: datetime, end: datetime) -> list[str]:
    """生成 start 到 end 之间的所有日期字符串列表。"""
    dates: list[str] = []
    current = start
    while current <= end:
        dates.append(current.strftime(DATE_FMT))
        current += timedelta(days=1)
    return dates


# ─────────────────────────────────────────────────────────────────────────────
# 构建排班表（日历表格式）
# ─────────────────────────────────────────────────────────────────────────────

def build_schedule_table(
    records: list[dict],
    user_ids: list[str],
    user_names: dict[str, str],
    class_map: dict[int, str],
    date_range: list[str],
) -> tuple[list[str], list[list[str]]]:
    """构建日历表格式的排班表。

    Returns:
        (headers, rows)
        headers = ["员工姓名", "05-19\n周一", "05-20\n周二", ...]
        rows    = [["张三", "早班", "早班", "休息", ...], ...]
    """
    # 构建 (userId, date) → 班次显示文本
    schedule_lookup: dict[tuple[str, str], str] = {}
    for record in records:
        uid = str(record.get("userId") or record.get("userid") or "")
        work_date = normalize_work_date(record.get("workDate") or record.get("work_date"))
        if not uid or not work_date:
            continue

        is_rest = str(record.get("isRest") or record.get("is_rest") or "N").upper()
        raw_cid = record.get("classId") or record.get("class_id") or 0
        raw_cname = record.get("className") or record.get("class_name") or ""

        if is_rest == "Y":
            display = "休息"
        elif raw_cname and str(raw_cname).strip():
            display = str(raw_cname).strip()
        else:
            cid = int(raw_cid) if raw_cid else 0
            if cid in class_map:
                display = class_map[cid]
            elif cid == 0:
                display = "休息"
            else:
                display = f"班次{cid}"

        schedule_lookup[(uid, work_date)] = display

    # 表头
    headers = ["员工姓名"]
    for date_str in date_range:
        dt = datetime.strptime(date_str, DATE_FMT)
        weekday = WEEKDAY_NAMES[dt.weekday()]
        headers.append(f"{date_str[5:]}\n{weekday}")

    # 数据行
    rows: list[list[str]] = []
    for uid in user_ids:
        name = user_names.get(uid, uid)
        row = [name]
        for date_str in date_range:
            row.append(schedule_lookup.get((uid, date_str), ""))
        rows.append(row)

    return headers, rows


# ─────────────────────────────────────────────────────────────────────────────
# 摘要输出
# ─────────────────────────────────────────────────────────────────────────────

def print_summary(
    rows: list[list[str]],
    date_range: list[str],
    out_path: str,
    record_count: int,
) -> None:
    """输出排班查询摘要到 stdout。"""
    out_abs = os.path.abspath(out_path)
    print(f"\n✅ 排班表导出成功！")
    print(f"   文件: {out_abs}")
    print(f"   人数: {len(rows)}")
    print(f"   日期: {date_range[0]} ~ {date_range[-1]} ({len(date_range)} 天)")
    print(f"   记录: {record_count} 条")

    # 预览前 10 人 × 前 7 天
    preview_rows = min(len(rows), 10)
    preview_cols = min(len(date_range), 7)
    if preview_rows > 0:
        print(f"\n排班预览（前 {preview_rows} 人 × 前 {preview_cols} 天）:")
        header_line = f"{'姓名':<10}" + "".join(
            f"{d[5:]:<8}" for d in date_range[:preview_cols]
        )
        print(header_line)
        print("-" * len(header_line))
        for row in rows[:preview_rows]:
            line = f"{row[0]:<10}" + "".join(
                f"{cell:<8}" for cell in row[1:preview_cols + 1]
            )
            print(line)
        if len(date_range) > preview_cols:
            print(f"   ... 共 {len(date_range)} 天，完整数据见 Excel")
        if len(rows) > preview_rows:
            print(f"   ... 共 {len(rows)} 人，完整数据见 Excel")


def main() -> None:
    parser = argparse.ArgumentParser(
        description="考勤排班查询导出（排班表格式）",
        epilog="执行前必须阅读 attendance-schedule.md",
    )
    parser.add_argument("--users", required=True, help="userId 列表，逗号分隔（必填）")
    parser.add_argument("--start", required=True, help="开始日期 YYYY-MM-DD（必填）")
    parser.add_argument("--end", required=True, help="结束日期 YYYY-MM-DD（必填）")
    parser.add_argument("--output", default="", help="输出文件路径（可选）")
    args = parser.parse_args()

    # ── 解析参数 ──
    user_ids = [uid.strip() for uid in args.users.split(",") if uid.strip()]
    if not user_ids:
        error("--users 不能为空")
        raise SystemExit(1)

    try:
        start_dt = parse_datetime_arg(args.start)
        end_dt = parse_datetime_arg(args.end, end_of_day=True)
    except ValueError as exc:
        error(str(exc))
        raise SystemExit(1) from exc

    start_date = start_dt.strftime(DATE_FMT)
    end_date = end_dt.strftime(DATE_FMT)

    if end_dt < start_dt:
        error(f"结束日期 {end_date} 早于开始日期 {start_date}")
        raise SystemExit(1)

    output_path = args.output or f"attendance_schedule_{start_date}_{end_date}.xlsx"

    log(f"🗓️ 排班查询: {len(user_ids)} 人, {start_date} ~ {end_date}")

    # ── 阶段 1: 查询排班记录（分批） ──
    records = fetch_all_schedules(user_ids, start_date, end_date)
    if not records:
        print(f"⚠️ 未查询到排班记录 ({start_date} ~ {end_date})")
        return

    # ── 阶段 2: 构建班次名称映射 ──
    class_map = build_class_name_map(records)

    # ── 阶段 3: 解析员工姓名 ──
    user_names = resolve_user_names(user_ids)

    # ── 阶段 4: 生成日期范围 & 构建排班表 ──
    date_range = generate_date_range(start_dt, end_dt)
    headers, rows = build_schedule_table(
        records, user_ids, user_names, class_map, date_range,
    )

    # ── 阶段 5: 输出 Excel ──
    title = f"排班表  {start_date} 至 {end_date}"
    subtitle = f"生成时间：{datetime.now().strftime(DATETIME_FMT)}  共 {len(rows)} 人"

    write_excel(
        output_path,
        headers,
        rows,
        sheet_name="排班表",
        title=title,
        subtitle=subtitle,
    )

    log(f"📄 Excel 已保存: {os.path.abspath(output_path)}")

    # ── 阶段 6: 输出摘要 ──
    print_summary(rows, date_range, output_path, len(records))


if __name__ == "__main__":
    main()
