from __future__ import annotations

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

from openpyxl import load_workbook


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


FLOWS = {
    "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-034",
        "涉及模块": "上游发单 -> 订单验收 -> 丸掌柜发起售后",
        "适用场景": "验证待验收阶段丸掌柜发起售后后取消，实际平台中丸掌柜侧子单列表返回异常中，后续可继续对异常中子单发起售后。",
        "数据复用建议": "取消售后后不要接主单申诉；同一订单继续复用到 S-FLOW-034，验证异常中子单行内发起售后入口。",
        "注意事项/缺口": "S-FLOW-048 后以丸掌柜详情子单列表实际状态为准；若子单行为异常中，则后续接 S-FLOW-034，不接 S-FLOW-049/S-FLOW-050。",
    },
    "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-049 -> S-FLOW-054 -> S-FLOW-055",
        "涉及模块": "上游发单 -> 订单验收 -> 处罚追缴",
        "适用场景": "验证子单验收成功后，丸掌柜对子单发起处罚，丸友集同意赔偿分支。",
        "数据复用建议": "使用新订单完整走验收链路；S-FLOW-049 后必须先执行 S-FLOW-054，再执行 S-FLOW-055。",
        "注意事项/缺口": "不经过待验收阶段售后取消；与 FLOW-047 使用不同订单。",
    },
    "FLOW-047": {
        "链路编号": "FLOW-047",
        "链路名称": "验收成功子单处罚驳回",
        "链路类型": "验收成功子单处罚追缴逆向",
        "起点状态": "打手提交验收；丸友集同意验收；丸友集申请上游验收；丸掌柜同意验收",
        "终点状态": "已完成；验收成功子单处罚驳回",
        "用例编号序列": "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-026 -> S-FLOW-040 -> S-FLOW-043 -> S-FLOW-045 -> S-FLOW-049 -> S-FLOW-054 -> S-FLOW-056",
        "涉及模块": "上游发单 -> 订单验收 -> 处罚追缴",
        "适用场景": "验证子单验收成功后，丸掌柜对子单发起处罚，丸友集驳回处罚分支。",
        "数据复用建议": "新增最大编号后的链路；保留既有 FLOW-046 不变；与 FLOW-045 使用不同订单。",
        "注意事项/缺口": "S-FLOW-049 后必须先执行 S-FLOW-054，再执行 S-FLOW-056。",
    },
}


CASE_UPDATES = {
    "S-FLOW-048": {
        "前置条件": "已执行 S-FLOW-046，丸掌柜在待验收阶段发起主单售后，当前订单为售后中且未处理完成。",
        "预期结果": "1. 丸掌柜取消待验收阶段售后提交成功。\n2. 丸友集侧目标订单不再显示售后中。\n3. 按实际平台回读，丸掌柜详情子单列表目标子单返回异常中或页面对应异常状态。\n4. 后续可继续衔接 S-FLOW-034，对异常中子单行内发起售后。",
        "跨端/状态断言": "1. 取消售后后不强制进入上游待验收完成链路。\n2. 若丸掌柜侧子单列表返回异常中，则 FLOW-030 后续接 S-FLOW-034，不接 S-FLOW-049/S-FLOW-050。\n3. 丸友集与丸掌柜侧来源单号、订单标题、主单号匹配同一订单。",
        "可组合/被引用关系": "承接 S-FLOW-046；被 FLOW-030 引用；后续可接 S-FLOW-034。",
        "备注": "2026-07-02 按实测修正：待验收阶段取消售后后丸掌柜子单列表返回异常中，FLOW-030 后续改接异常中子单发起售后。",
    },
    "S-FLOW-054": {
        "可组合/被引用关系": "承接 S-FLOW-049 后的已验收/验收成功子单；后续分支为 S-FLOW-055/S-FLOW-056；被 FLOW-045、FLOW-047 引用。",
        "备注": "2026-07-02 修正：验收成功子单处罚分支放在 FLOW-045/FLOW-047，原 FLOW-030 保持售后取消后续链路。",
    },
    "S-FLOW-055": {
        "可组合/被引用关系": "承接 S-FLOW-054；FLOW-045 处罚同意赔偿分支引用。",
        "备注": "2026-07-02 修正：作为验收成功子单处罚同意赔偿分支。",
    },
    "S-FLOW-056": {
        "可组合/被引用关系": "承接 S-FLOW-054；FLOW-047 处罚驳回分支引用。",
        "备注": "2026-07-02 修正：作为验收成功子单处罚驳回分支；不占用既有 FLOW-046。",
    },
}


def header_map(ws) -> dict[str, int]:
    return {
        str(ws.cell(1, col).value or "").strip(): col
        for col in range(1, ws.max_column + 1)
        if ws.cell(1, 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 row_by_value(ws, col: int) -> dict[str, int]:
    result: dict[str, int] = {}
    for row in range(2, ws.max_row + 1):
        value = str(ws.cell(row, col).value or "").strip()
        if value and value not in result:
            result[value] = row
    return result


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 load_case_rows() -> dict[str, dict[str, str]]:
    wb = load_workbook(CASE_BOOK, data_only=True)
    ws = wb["订单状态流转"]
    h = header_map(ws)
    rows: dict[str, dict[str, str]] = {}
    for row in range(2, ws.max_row + 1):
        case_id = str(ws.cell(row, h["用例编号"]).value or "").strip()
        if case_id:
            rows[case_id] = {key: str(ws.cell(row, col).value or "") for key, col in h.items()}
    return rows


def enrich_flow(flow: dict[str, str], case_rows: dict[str, dict[str, str]]) -> dict[str, str]:
    case_ids = [item.strip() for item in flow["用例编号序列"].split("->") if item.strip()]
    refs = []
    modules = []
    for case_id in case_ids:
        case = case_rows.get(case_id, {})
        refs.append(f"{case_id}({case.get('用例标题', '')})")
        module = case.get("所属模块", "")
        if module and module not in modules:
            modules.append(module)
    enriched = dict(flow)
    enriched["用例链路（编号+标题）"] = " -> ".join(refs)
    enriched["涉及模块"] = flow.get("涉及模块") or " -> ".join(modules)
    return enriched


def update_case_book() -> None:
    backup = CASE_BOOK.with_name(f"{CASE_BOOK.stem}.flow030_045_047前备份_{datetime.now():%Y%m%d%H%M%S}{CASE_BOOK.suffix}")
    shutil.copyfile(CASE_BOOK, backup)
    wb = load_workbook(CASE_BOOK)
    ws = wb["订单状态流转"]
    h = header_map(ws)
    rows = row_by_value(ws, h["用例编号"])
    for case_id, values in CASE_UPDATES.items():
        row = rows.get(case_id)
        if row:
            set_row_values(ws, row, values)
    wb.save(CASE_BOOK)


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 "").strip()
        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 update_flow_summary(ws, case_rows: dict[str, dict[str, str]]) -> None:
    h = header_map(ws)
    rows = row_by_value(ws, h["链路编号"])
    for flow_id in ["FLOW-030", "FLOW-045"]:
        set_row_values(ws, rows[flow_id], enrich_flow(FLOWS[flow_id], case_rows))
    rows = row_by_value(ws, h["链路编号"])
    if "FLOW-047" not in rows:
        anchor = rows["FLOW-045"]
        ws.insert_rows(anchor + 1)
        copy_row_style(ws, anchor, anchor + 1)
        target_row = anchor + 1
    else:
        target_row = rows["FLOW-047"]
    set_row_values(ws, target_row, enrich_flow(FLOWS["FLOW-047"], case_rows))


def update_flow_details(ws, case_rows: dict[str, dict[str, str]]) -> None:
    insert_at = delete_detail_rows(ws, {"FLOW-030", "FLOW-045", "FLOW-047"})
    style_row = max(2, min(insert_at - 1, ws.max_row))
    rows_to_add: list[list[str]] = []
    for flow_id in ["FLOW-030", "FLOW-045", "FLOW-047"]:
        case_ids = [item.strip() for item in FLOWS[flow_id]["用例编号序列"].split("->") if item.strip()]
        for index, case_id in enumerate(case_ids, start=1):
            case = case_rows.get(case_id, {})
            rows_to_add.append(
                [
                    flow_id,
                    str(index),
                    case_id,
                    case.get("所属模块", ""),
                    case.get("用例标题", ""),
                    case.get("前置条件", ""),
                    case.get("预期结果", ""),
                ]
            )
    for offset, values in enumerate(rows_to_add):
        row = insert_at + offset
        ws.insert_rows(row)
        copy_row_style(ws, style_row, row)
        for col, value in enumerate(values, start=1):
            ws.cell(row, col).value = value


def recompute_coverage(wb, case_rows: dict[str, dict[str, str]]) -> None:
    summary = wb["链路流程线"]
    hs = 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, hs["链路编号"]).value or "").strip()
        seq = str(summary.cell(row, hs["用例编号序列"]).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_value(ws, h["用例编号"])
    template_row = max(rows.values()) if rows else 2
    for case_id, flows in flow_by_case.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)
        case = case_rows.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"}:
            values["备注"] = "验收成功子单处罚分支覆盖"
        if case_id == "S-FLOW-048":
            values["备注"] = "FLOW-030 取消售后后接异常中子单售后"
        set_row_values(ws, row, values)


def update_flow_book() -> None:
    backup = FLOW_BOOK.with_name(f"{FLOW_BOOK.stem}.flow030_045_047前备份_{datetime.now():%Y%m%d%H%M%S}{FLOW_BOOK.suffix}")
    shutil.copyfile(FLOW_BOOK, backup)
    case_rows = load_case_rows()
    wb = load_workbook(FLOW_BOOK)
    update_flow_summary(wb["链路流程线"], case_rows)
    update_flow_details(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 取消售后后接 S-FLOW-034；FLOW-045 为处罚同意；新增 FLOW-047 为处罚驳回，保留既有 FLOW-046。",
            ]
        )
    wb.save(FLOW_BOOK)


def main() -> None:
    update_case_book()
    update_flow_book()
    print("Synced FLOW-030/FLOW-045/FLOW-047 without rebuilding workbook styles.")


if __name__ == "__main__":
    main()
