from __future__ import annotations

from copy import copy
from datetime import datetime
from pathlib import Path
import shutil

import openpyxl


ROOT = Path(__file__).resolve().parents[1]
TESTCASE_DIR = ROOT / "测试用例"
FLOW_BOOK = TESTCASE_DIR / "订单状态流转_链路流程线整理.xlsx"
FLOW_BACKUP = TESTCASE_DIR / "订单状态流转_链路流程线整理 - 副本.xlsx"
CASE_BOOK = TESTCASE_DIR / "SaaS_E2E测试用例_单Sheet用例测试列表.xlsx"
CASE_BACKUP = TESTCASE_DIR / "SaaS_E2E测试用例_单Sheet用例测试列表 - 副本.xlsx"


FLOW_030 = {
    "链路编号": "FLOW-030",
    "链路名称": "待验收阶段-发起售后-取消售后-丸掌柜同意验收-验收成功子单处罚同意赔偿",
    "用例编号序列": "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-026 -> S-FLOW-040 -> S-FLOW-043 -> S-FLOW-045 -> S-FLOW-046 -> S-FLOW-048 -> S-FLOW-049 -> S-FLOW-054 -> S-FLOW-055",
    "起点状态": "主单待验收；子单待验收",
    "终点状态": "已完成；验收成功子单处罚同意赔偿",
    "链路类型": "待验收售后取消后回归验收 + 处罚追缴正向",
    "适用场景": "验证待验收阶段售后取消后恢复验收前状态，再走丸掌柜同意验收，并对子单验收成功状态发起处罚追缴。",
    "数据复用建议": "取消售后后同一订单可继续普通验收；丸掌柜同意上游验收后，子单列表验收成功状态可衔接 S-FLOW-054，再由 S-FLOW-055 覆盖同意赔偿分支。",
    "注意事项/缺口": "S-FLOW-049 后必须先执行 S-FLOW-054；同意赔偿分支由 S-FLOW-055 覆盖。",
}

FLOW_045 = {
    "链路编号": "FLOW-045",
    "链路名称": "待验收阶段-发起售后-取消售后-丸掌柜同意验收-验收成功子单处罚驳回",
    "用例编号序列": "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-026 -> S-FLOW-040 -> S-FLOW-043 -> S-FLOW-045 -> S-FLOW-046 -> S-FLOW-048 -> S-FLOW-049 -> S-FLOW-054 -> S-FLOW-056",
    "起点状态": "主单待验收；子单待验收",
    "终点状态": "已完成；验收成功子单处罚驳回",
    "链路类型": "待验收售后取消后回归验收 + 处罚追缴逆向",
    "适用场景": "验证待验收阶段售后取消后恢复验收前状态，再走丸掌柜同意验收，并对子单验收成功状态发起处罚追缴后由丸友集驳回。",
    "数据复用建议": "与 FLOW-030 使用不同订单；S-FLOW-049 后同样先接 S-FLOW-054，再由 S-FLOW-056 覆盖驳回处罚分支。",
    "注意事项/缺口": "S-FLOW-049 后必须先执行 S-FLOW-054；驳回处罚分支由 S-FLOW-056 覆盖。",
}

FLOW_025 = {
    "链路编号": "FLOW-025",
    "链路名称": "打手提交验收-丸友集同意验收-打手发起结算-丸友集完成结算-续发下一子单",
    "用例编号序列": "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-026 -> S-FLOW-040 -> S-FLOW-043 -> S-FLOW-080 -> S-FLOW-081 -> S-FLOW-044",
    "终点状态": "前一子单已验收并已完成结算；下一子单待接单/代练中",
    "链路类型": "子单验收结算后多子单续发",
    "适用场景": "验证丸友集同意验收后，需先由打手发起结算、丸友集完成结算，使前一子单从已验收转为已完成，再继续发下一子单。",
    "数据复用建议": "S-FLOW-043 后不直接接 S-FLOW-044；先接 S-FLOW-080/S-FLOW-081 完成打手结算，主单不应因单个子单完成直接完结。",
}

CASE_UPDATES = {
    "S-FLOW-054": {
        "跨端/状态断言": "1. 处罚记录的主单号、来源单号、子单号、责任方、金额在丸掌柜侧一致。\n"
        "2. 处罚只作用于目标子单，不误影响同主单其他子单。\n"
        "3. 后续由 S-FLOW-055 或 S-FLOW-056 验证丸友集处理结果。",
        "可组合/被引用关系": "承接 S-FLOW-049 后的已验收/验收成功子单；后续分支为 S-FLOW-055/S-FLOW-056；被 FLOW-030、FLOW-045 引用。",
        "自动化状态": "已自动化",
        "备注": "2026-07-02 修正：S-FLOW-049 后接验收成功子单处罚，后续分支为 S-FLOW-055/S-FLOW-056。",
    },
    "S-FLOW-055": {
        "用例标题": "【丸友集】验收成功子单处罚-同意赔偿",
        "预期结果": "1. 丸友集侧目标处罚/申诉记录处理完成或显示已完成/已通过。\n"
        "2. 处罚金额等于申请处罚金额。\n"
        "3. 丸掌柜侧子单处罚单同步为已完成或同意赔偿结果。",
        "跨端/状态断言": "丸友集申诉工单、丸掌柜子单处罚单按丸掌柜主单号/来源单号可定位同一记录；处理状态和金额一致。",
        "可组合/被引用关系": "承接 S-FLOW-054；FLOW-030 处罚同意赔偿分支引用。",
        "自动化状态": "已自动化",
        "备注": "2026-07-02 修正：作为 S-FLOW-054 后续同意赔偿分支，不再仅作为 S-FLOW-063 别名。",
    },
    "S-FLOW-056": {
        "用例标题": "【丸友集】验收成功子单处罚-驳回处罚",
        "预期结果": "1. 丸友集侧目标处罚/申诉记录处理完成或显示已拒绝/已驳回。\n"
        "2. 处罚金额为0或页面对应未赔偿结果。\n"
        "3. 丸掌柜侧子单处罚单同步为已拒绝或驳回结果。",
        "跨端/状态断言": "丸友集申诉工单、丸掌柜子单处罚单按丸掌柜主单号/来源单号可定位同一记录；驳回状态和金额结果一致。",
        "可组合/被引用关系": "承接 S-FLOW-054；FLOW-045 处罚驳回分支引用。",
        "自动化状态": "已自动化",
        "备注": "2026-07-02 修正：作为 S-FLOW-054 后续驳回处罚分支，不再仅作为 S-FLOW-064 别名。",
    },
}


def header_map(ws) -> dict[str, int]:
    return {str(ws.cell(1, col).value or ""): col for col in range(1, ws.max_column + 1) if ws.cell(1, col).value}


def row_by_key(ws, key_col: int) -> dict[str, int]:
    return {str(ws.cell(row, key_col).value or ""): row for row in range(2, ws.max_row + 1) if ws.cell(row, key_col).value}


def copy_cell_style(src, dst) -> None:
    if src.has_style:
        dst._style = copy(src._style)
    dst.font = copy(src.font)
    dst.fill = copy(src.fill)
    dst.border = copy(src.border)
    dst.alignment = copy(src.alignment)
    dst.number_format = src.number_format
    dst.protection = copy(src.protection)


def copy_row_style(ws, src_row: int, dst_row: int) -> None:
    ws.row_dimensions[dst_row].height = ws.row_dimensions[src_row].height
    for col in range(1, ws.max_column + 1):
        copy_cell_style(ws.cell(src_row, col), ws.cell(dst_row, col))


def set_row_values(ws, row: int, values: dict[str, str]) -> None:
    h = header_map(ws)
    for key, value in values.items():
        if key in h:
            ws.cell(row, h[key]).value = value


def first_row(ws, key: str) -> int | None:
    h = header_map(ws)
    rows = row_by_key(ws, h[next(iter(h))])
    return rows.get(key)


def insert_flow_after(ws, after_flow_id: str, values: dict[str, str]) -> int:
    h = header_map(ws)
    rows = row_by_key(ws, h["链路编号"])
    old = rows.get(values["链路编号"])
    if old:
        ws.delete_rows(old)
        rows = row_by_key(ws, h["链路编号"])
    after_row = rows[after_flow_id]
    ws.insert_rows(after_row + 1)
    copy_row_style(ws, after_row, after_row + 1)
    set_row_values(ws, after_row + 1, values)
    return after_row + 1


def case_title(case_rows: dict[str, dict[str, str]], case_id: str) -> str:
    return case_rows.get(case_id, {}).get("用例标题", "")


def build_refs(case_rows: dict[str, dict[str, str]], case_ids: list[str]) -> str:
    return " -> ".join(f"{case_id}({case_title(case_rows, case_id)})" for case_id in case_ids)


def build_modules(case_rows: dict[str, dict[str, str]], case_ids: list[str]) -> str:
    modules: list[str] = []
    for case_id in case_ids:
        module = case_rows.get(case_id, {}).get("所属模块", "")
        if module and module not in modules:
            modules.append(module)
    return " -> ".join(modules)


def load_case_rows() -> dict[str, dict[str, str]]:
    wb = openpyxl.load_workbook(CASE_BOOK, data_only=True)
    ws = wb["订单状态流转"]
    h = header_map(ws)
    result: dict[str, dict[str, str]] = {}
    for row in range(2, ws.max_row + 1):
        case_id = str(ws.cell(row, h["用例编号"]).value or "")
        if not case_id:
            continue
        result[case_id] = {key: str(ws.cell(row, col).value or "") for key, col in h.items()}
    return result


def enrich_flow(values: dict[str, str], case_rows: dict[str, dict[str, str]]) -> dict[str, str]:
    case_ids = [item.strip() for item in values["用例编号序列"].split("->")]
    enriched = dict(values)
    enriched["用例链路（编号+标题）"] = build_refs(case_rows, case_ids)
    enriched["涉及模块"] = build_modules(case_rows, case_ids)
    return enriched


def sync_flow_summary(wb, case_rows: dict[str, dict[str, str]]) -> None:
    ws = wb["链路流程线"]
    h = header_map(ws)
    rows = row_by_key(ws, h["链路编号"])
    for values in [FLOW_025, FLOW_030]:
        set_row_values(ws, rows[values["链路编号"]], enrich_flow(values, case_rows))
    insert_flow_after(ws, "FLOW-030", enrich_flow(FLOW_045, case_rows))


def delete_detail_rows(ws, flow_ids: set[str]) -> int:
    insert_at = ws.max_row + 1
    for row in range(ws.max_row, 1, -1):
        flow_id = str(ws.cell(row, 1).value or "")
        if flow_id in flow_ids:
            insert_at = min(insert_at, row)
            ws.delete_rows(row)
    return insert_at if insert_at <= ws.max_row + 1 else ws.max_row + 1


def append_detail_rows(wb, case_rows: dict[str, dict[str, str]]) -> None:
    ws = wb["链路步骤明细"]
    insert_at = delete_detail_rows(ws, {"FLOW-030", "FLOW-045"})
    style_row = max(2, insert_at - 1)
    rows_to_add: list[list[str]] = []
    for flow in [FLOW_030, FLOW_045]:
        case_ids = [item.strip() for item in flow["用例编号序列"].split("->")]
        for index, case_id in enumerate(case_ids, start=1):
            case = case_rows.get(case_id, {})
            rows_to_add.append(
                [
                    flow["链路编号"],
                    str(index),
                    case_id,
                    case.get("所属模块", ""),
                    case.get("用例标题", ""),
                    case.get("前置条件", ""),
                    case.get("预期结果", ""),
                ]
            )
    for offset, values in enumerate(rows_to_add):
        ws.insert_rows(insert_at + offset)
        copy_row_style(ws, style_row, insert_at + offset)
        for col, value in enumerate(values, start=1):
            ws.cell(insert_at + offset, col).value = value


def recompute_coverage(wb, case_rows: dict[str, dict[str, str]]) -> None:
    summary = wb["链路流程线"]
    h_summary = header_map(summary)
    flow_by_case: dict[str, list[str]] = {}
    for row in range(2, summary.max_row + 1):
        flow_id = str(summary.cell(row, h_summary["链路编号"]).value or "")
        seq = str(summary.cell(row, h_summary["用例编号序列"]).value or "")
        for case_id in [item.strip() for item in seq.split("->") if item.strip()]:
            flow_by_case.setdefault(case_id, []).append(flow_id)

    ws = wb["用例覆盖索引"]
    h = header_map(ws)
    rows = row_by_key(ws, h["用例编号"])
    template_row = max(rows.values()) if rows else 2
    for case_id, case in case_rows.items():
        if case_id in rows:
            row = rows[case_id]
        else:
            row = ws.max_row + 1
            ws.insert_rows(row)
            copy_row_style(ws, template_row, row)
            rows[case_id] = row
        flows = flow_by_case.get(case_id, [])
        values = {
            "用例编号": case_id,
            "所属模块": case.get("所属模块", ""),
            "用例标题": case.get("用例标题", ""),
            "覆盖次数": str(len(flows)),
            "出现链路": "、".join(flows),
            "是否已覆盖": "是" if flows else "否",
            "备注": "新增处罚分支覆盖" if case_id in {"S-FLOW-054", "S-FLOW-055", "S-FLOW-056"} else "",
        }
        for key, value in values.items():
            if key in h:
                ws.cell(row, h[key]).value = value


def update_case_book_preserve_styles() -> None:
    wb = openpyxl.load_workbook(CASE_BOOK)
    ws = wb["订单状态流转"]
    h = header_map(ws)
    rows = row_by_key(ws, h["用例编号"])
    for case_id, updates in CASE_UPDATES.items():
        row = rows.get(case_id)
        if not row:
            continue
        for key, value in updates.items():
            if key in h:
                ws.cell(row, h[key]).value = value
    wb.save(CASE_BOOK)


def restore_flow_book() -> None:
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
    shutil.copyfile(FLOW_BOOK, TESTCASE_DIR / f"订单状态流转_链路流程线整理.样式修复前备份_{timestamp}.xlsx")

    case_rows = load_case_rows()
    wb = openpyxl.load_workbook(FLOW_BACKUP)
    sync_flow_summary(wb, case_rows)
    append_detail_rows(wb, case_rows)
    recompute_coverage(wb, case_rows)
    if "说明" in wb.sheetnames:
        wb["说明"].append(["更新记录", f"{datetime.now():%Y-%m-%d %H:%M:%S} 从副本恢复样式，并保留 FLOW-030/新增 FLOW-045 处罚分支。"])
    wb.save(FLOW_BOOK)


def main() -> None:
    update_case_book_preserve_styles()
    restore_flow_book()
    print("Restored Excel styles from backups and kept FLOW-030/FLOW-045 updates.")


if __name__ == "__main__":
    main()
