"""
Excel 解析服务 — 批量导入核心逻辑

业务规则：
  - 前置校验：所有涉及年份的 yearly_targets 必须存在，否则整体报错（不入库任何行）
  - 幂等：检测到重复 (year, episode_number) → 整体 409，不入库
  - 事务：逐行提交，成功立即 commit，失败行收集到 errors[]
  - 软引用+快照：编导姓名匹配不到在职用户 → editor_id=NULL + editor_name_snapshot=姓名
  - is_rerun 解析：接受是/否、true/false、1/0，大小写不敏感
  - Phase 2 不支持重播行（is_rerun=是 → 报错标记为失败行）
"""

import io
import uuid
from datetime import date
from typing import Any

import pandas as pd
from sqlmodel import Session, select

from app.models.episode import Episode
from app.models.user import User
from app.models.yearly_target import YearlyTarget


TRUTHY = {"是", "true", "1", "yes"}
FALSY = {"否", "false", "0", "no"}


def parse_bool(val: Any) -> bool | None:
    """解析布尔值，接受多种格式。"""
    if val is None:
        return None
    v = str(val).strip().lower()
    if v == "":
        return None
    if v in TRUTHY:
        return True
    if v in FALSY:
        return False
    raise ValueError(f"无法解析布尔值: {val!r}")


def parse_date(val: Any) -> date | None:
    """解析日期，支持 YYYY-MM-DD 和 YYYY/MM/DD。"""
    if val is None or str(val).strip() == "":
        return None
    v = str(val).strip()
    for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%Y%m%d"):
        try:
            return date.fromisoformat(v)
        except ValueError:
            pass
    raise ValueError(f"无法解析日期: {val!r}")


def parse_float(val: Any) -> float | None:
    """解析浮点数，接受百分比字符串。"""
    if val is None:
        return None
    v = str(val).strip()
    if v == "":
        return None
    if v.endswith("%"):
        v = v[:-1]
    return float(v)


def find_editor_by_name(session: Session, name: str):
    """按 display_name 匹配在职编导。匹配上返 (id, name)，匹配不上返 (None, name)。"""
    if not name or str(name).strip() == "":
        return None, ""
    name = str(name).strip()
    user = session.exec(
        select(User).where(User.display_name == name, User.is_active == True)
    ).first()
    if user:
        return user.id, name
    return None, name


class ExcelService:
    def __init__(self, session: Session):
        self.session = session
        self.errors: list[dict] = []
        self.batch_id = str(uuid.uuid4())

    def validate_yearly_targets(self, air_dates: list[date]):
        """前置校验：所有涉及年份的 yearly_targets 必须存在，否则整体报错。"""
        distinct_years = set(dt.year for dt in air_dates if dt is not None)
        missing = []
        for yr in sorted(distinct_years):
            exists = self.session.exec(
                select(YearlyTarget).where(YearlyTarget.year == yr)
            ).first()
            if not exists:
                missing.append(yr)
        if missing:
            raise ValueError(
                f"导入前请先录入以下年份的年度目标：{', '.join(map(str, missing))}。"
                "请先在年度目标页录入目标后再导入。"
            )

    def check_duplicates(self, rows: list[dict]):
        """检测重复 (year, episode_number)，有重复则整体报错。"""
        seen = set()
        duplicates = []
        for row in rows:
            key = (row["_air_year"], row["episode_number"])
            if key in seen:
                duplicates.append(row)
            seen.add(key)
        if duplicates:
            dup_list = [
                f"{r['_air_year']}年第 {r['episode_number']} 期"
                for r in duplicates
            ]
            raise ValueError(
                f"文件中以下期次与库中记录重复：{', '.join(dup_list)}。"
                "请先手动删除重复期次后再重新导入。"
            )

    def import_episodes(self, file_content: bytes) -> dict:
        """解析 Excel，批量导入 episodes。返回 ImportResult 结构。"""
        # 1. 读取 Excel（pandas 2.x 要求 file-like object，用 BytesIO 包装 bytes）
        file_like = io.BytesIO(file_content)
        df = pd.read_excel(file_like, engine="openpyxl")
        rows = df.to_dict(orient="records")

        # 2. 解析 air_date 并提取年份
        parsed_rows = []
        air_dates = []
        for i, row in enumerate(rows, start=2):  # Excel 行号从2开始（第1行=表头）
            raw = dict(row)
            try:
                air_dt = parse_date(row.get("air_date"))
                if air_dt is None:
                    raise ValueError("air_date 不能为空")
                parsed_rows.append({
                    "_row_number": i,
                    "_air_year": air_dt.year,
                    "_air_date": air_dt,
                    "episode_number": int(row["episode_number"]),
                    "program_name": str(row["program_name"]).strip(),
                    "audience_share": parse_float(row.get("audience_share")),
                    "audience_rating": parse_float(row.get("audience_rating")),
                    "is_rerun": parse_bool(row.get("is_rerun")),
                    "editor_name_snapshot": str(row.get("editor_name", "")).strip() or "未知编导",
                    "notes": str(row.get("notes", "")).strip() or None,
                    "_raw": raw,
                })
                air_dates.append(air_dt)
            except Exception as e:
                self.errors.append({
                    "row_number": i,
                    "reason": str(e),
                    "raw_data": raw,
                })

        # 3. 前置校验：年份 targets
        try:
            self.validate_yearly_targets(air_dates)
        except ValueError:
            raise  # 直接抛给调用方，整体 400

        # 4. 重复检测
        try:
            self.check_duplicates(parsed_rows)
        except ValueError:
            raise  # 直接抛给调用方，整体 409

        # 5. 逐行入库
        for row in parsed_rows:
            try:
                self._import_one_row(row)
            except Exception as e:
                self.errors.append({
                    "row_number": row["_row_number"],
                    "reason": str(e),
                    "raw_data": row["_raw"],
                })

        # 6. 计算结果
        total = len(rows)
        success = total - len(self.errors)
        return {
            "batch_id": self.batch_id,
            "total_rows": total,
            "success_count": success,
            "failed_count": len(self.errors),
            "errors": self.errors,
        }

    def _import_one_row(self, row: dict):
        """导入单行，处理 is_rerun 逻辑。"""
        # 重播行 Phase 2 不支持
        if row["is_rerun"] is True:
            raise ValueError(
                "Phase 2 不支持重播期次导入。"
                "有重播行请空着或用其他工具录入，系统已标记为失败行。"
            )

        # 编辑匹配
        editor_id, editor_name = find_editor_by_name(self.session, row["editor_name_snapshot"])
        if editor_id is None and row["editor_name_snapshot"] == "未知编导":
            editor_name = row["editor_name_snapshot"]

        # 插入
        episode = Episode(
            episode_number=row["episode_number"],
            program_name=row["program_name"],
            air_date=row["_air_date"],
            editor_id=editor_id,
            editor_name_snapshot=editor_name,
            audience_share=row["audience_share"],
            audience_rating=row["audience_rating"],
            is_rerun=False,
            original_episode_id=None,
            notes=row["notes"],
        )
        self.session.add(episode)
        self.session.commit()
        self.session.refresh(episode)


def generate_error_excel(errors: list[dict]) -> bytes:
    """生成失败行 Excel，供责编下载修正。"""
    import io
    from openpyxl import Workbook

    wb = Workbook()
    ws = wb.active
    ws.title = "失败行"

    # 表头
    headers = ["row_number", "reason"] + list(errors[0]["raw_data"].keys()) if errors else ["row_number", "reason"]
    ws.append(headers)

    # 失败行
    for err in errors:
        row_data = [err["row_number"], err["reason"]] + list(err["raw_data"].values())
        ws.append(row_data)

    output = io.BytesIO()
    wb.save(output)
    output.seek(0)
    return output.read()