from __future__ import annotations

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

from openpyxl import load_workbook
from openpyxl.styles import PatternFill


ROOT = Path(__file__).resolve().parents[1]
FLOW_EXCEL = ROOT / "订单状态流转_链路流程线整理.xlsx"
SINGLE_EXCEL = ROOT / "SaaS_E2E测试用例_单Sheet用例测试列表.xlsx"
BACKUP_DIR = ROOT / "runtime" / "excel_backups"
JSON_PATHS = [
    ROOT / "scripts" / "order_status_flow_cases.json",
    ROOT / "runtime" / "order_status_flow_cases_for_sheet.json",
    ROOT / "runtime" / "order_status_flow_cases_for_excel.json",
]


NEW_CASES = [
    {
        "用例编号": "S-FLOW-069",
        "用例层级": "订单状态流转用例",
        "来源/组合依据": "S-FLOW-016 实测结果：同意撤销后子单列表目标子单为撤销结束，且该子单行展示【处罚】入口。",
        "所属模块": "丸掌柜发起处罚追缴",
        "用例标题": "【丸掌柜】子单撤销结束状态-对撤销结束子单发起处罚",
        "登录平台/角色账号": "丸掌柜：企业主账号或处罚权限账号；丸友集：工作室主账号或处罚处理权限账号。",
        "前置条件": "已执行 S-FLOW-016，丸掌柜同意丸友集撤销；丸掌柜订单详情中目标子单状态为撤销结束，且目标子单操作列展示【处罚】入口；丸掌柜详情顶部主单状态/子单汇总状态不作为本用例硬性前置，只记录实际展示。",
        "测试数据": "来源单号、订单标题、丸掌柜主单号、撤销结束子单号、处罚对象、处罚类型、处罚金额、处罚原因、处罚凭证。",
        "执行步骤": "\n".join(
            [
                "1. 登录丸掌柜主账号或具备处罚权限的账号。",
                "2. 进入商家管理-订单管理。",
                "3. 按主单号、来源单号或订单标题查询已完成 S-FLOW-016 的目标订单。",
                "4. 打开订单详情，记录顶部主单状态和子单汇总状态，仅作为实际展示回读。",
                "5. 定位订单状态为撤销结束的目标子单。",
                "6. 点击该撤销结束子单操作列中的【处罚】入口。",
                "7. 选择处罚类型和处罚对象，填写整数处罚金额、处罚原因和说明。",
                "8. 按页面要求上传或关联处罚凭证。",
                "9. 提交处罚申请。",
                "10. 进入商家管理-子单处罚单，按主单号查询处罚记录并回读状态。",
                "11. 登录丸友集账号，进入工作室履约应用-申诉工单，按上游单号查询该处罚记录。",
            ]
        ),
        "预期结果": "\n".join(
            [
                "1. 丸掌柜可对撤销结束的目标子单发起处罚。",
                "2. 子单处罚记录生成成功，处罚类型、对象、金额、原因和目标子单一致。",
                "3. 原目标子单保持撤销结束，不被处罚申请错误回退。",
                "4. 丸友集工作室履约应用-申诉工单可查询到待处理处罚记录。",
            ]
        ),
        "跨端/状态断言": "\n".join(
            [
                "1. 本用例承接 S-FLOW-016，验证撤销结束子单处罚分支，不写入 S-FLOW-016 本身预期。",
                "2. 处罚只作用于目标撤销结束子单，不误影响同主单其他子单。",
                "3. 处罚金额输入框只填写整数。",
                "4. 处罚记录中的主单号、上游单号、子单号、责任方和金额在丸掌柜与丸友集侧一致。",
                "5. 丸掌柜详情顶部主单状态/子单汇总状态只回读记录，不限制为固定状态。",
            ]
        ),
        "可组合/被引用关系": "承接 S-FLOW-016；后续可接 S-FLOW-070/S-FLOW-071 处理处罚。",
        "自动化状态": "已自动化",
        "备注": "2026-06-24新增：从同意撤销后的撤销结束子单拆出的处罚分支。",
    },
    {
        "用例编号": "S-FLOW-070",
        "用例层级": "订单状态流转用例",
        "来源/组合依据": "S-FLOW-069 发起撤销结束子单处罚后，丸友集申诉工单处理同意赔偿。",
        "所属模块": "处罚追缴-丸友集处理",
        "用例标题": "【丸友集】撤销结束子单处罚追缴-同意处罚/赔偿",
        "登录平台/角色账号": "丸友集：工作室主账号或具备处罚/申诉处理权限的账号；丸掌柜：企业主账号或处罚权限账号。",
        "前置条件": "已执行 S-FLOW-069，丸掌柜已对撤销结束子单发起处罚追缴；丸友集工作室履约应用-申诉工单中按上游单号可查询到状态为【待处理】的处罚记录。",
        "测试数据": "上游单号、来源单号、处罚单号、处罚金额、处理备注。",
        "执行步骤": "\n".join(
            [
                "1. 登录丸友集账号。",
                "2. 进入工作室履约应用-申诉工单。",
                "3. 按上游单号查询目标处罚记录。",
                "4. 若同一上游单号存在多条处罚数据，定位状态为【待处理】的处罚订单。",
                "5. 点击操作列【去处理】或【我要处理】。",
                "6. 在处罚详情弹窗中点击【同意赔偿】或页面对应同意按钮。",
                "7. 填写处理备注并提交。",
                "8. 回到申诉工单列表按上游单号回读处理结果。",
                "9. 登录丸掌柜账号，进入商家管理-子单处罚单，按主单号回读处罚单状态和金额。",
            ]
        ),
        "预期结果": "\n".join(
            [
                "1. 丸友集申诉工单目标处罚记录处理结果为同意赔偿/已完成。",
                "2. 丸掌柜子单处罚单同步展示已完成或页面对应同意赔偿状态。",
                "3. 申请处罚金额与实际赔偿/处罚金额一致。",
                "4. 原撤销结束子单状态不被处罚处理错误回退。",
            ]
        ),
        "跨端/状态断言": "\n".join(
            [
                "1. 丸友集申诉工单与丸掌柜子单处罚单均展示同意/已完成结果。",
                "2. 多条处罚数据共用同一上游单号时，只能操作状态为【待处理】的处罚订单。",
                "3. 处罚金额两端一致，处理备注/结果两端可追溯。",
            ]
        ),
        "可组合/被引用关系": "承接 S-FLOW-069；撤销结束子单处罚同意分支。",
        "自动化状态": "已自动化",
        "备注": "2026-06-24新增：撤销结束子单处罚追缴同意分支。",
    },
    {
        "用例编号": "S-FLOW-071",
        "用例层级": "订单状态流转用例",
        "来源/组合依据": "S-FLOW-069 发起撤销结束子单处罚后，丸友集申诉工单处理驳回。",
        "所属模块": "处罚追缴-丸友集处理",
        "用例标题": "【丸友集】撤销结束子单处罚追缴-拒绝处罚/驳回申诉",
        "登录平台/角色账号": "丸友集：工作室主账号或具备处罚/申诉处理权限的账号；丸掌柜：企业主账号或处罚权限账号。",
        "前置条件": "已执行 S-FLOW-069，丸掌柜已对撤销结束子单发起处罚追缴；丸友集工作室履约应用-申诉工单中按上游单号可查询到状态为【待处理】的处罚记录。",
        "测试数据": "上游单号、来源单号、处罚单号、处罚金额、驳回原因。",
        "执行步骤": "\n".join(
            [
                "1. 登录丸友集账号。",
                "2. 进入工作室履约应用-申诉工单。",
                "3. 按上游单号查询目标处罚记录。",
                "4. 若同一上游单号存在多条处罚数据，定位状态为【待处理】的处罚订单。",
                "5. 点击操作列【去处理】或【我要处理】。",
                "6. 在处罚详情弹窗中点击【驳 回】或页面对应拒绝按钮。",
                "7. 填写驳回原因并提交。",
                "8. 回到申诉工单列表按上游单号回读处理结果。",
                "9. 登录丸掌柜账号，进入商家管理-子单处罚单，按主单号回读处罚单状态和金额。",
            ]
        ),
        "预期结果": "\n".join(
            [
                "1. 丸友集申诉工单目标处罚记录处理结果为驳回/已拒绝。",
                "2. 丸掌柜子单处罚单同步展示已拒绝或页面对应驳回状态。",
                "3. 实际处罚金额为 0 或页面对应未赔偿结果。",
                "4. 原撤销结束子单状态不被处罚驳回错误回退。",
            ]
        ),
        "跨端/状态断言": "\n".join(
            [
                "1. 丸友集申诉工单与丸掌柜子单处罚单均展示驳回/已拒绝结果。",
                "2. 驳回原因/处罚结果两端一致，不产生同意赔偿导致的扣减或赔偿流水。",
                "3. 多条处罚数据共用同一上游单号时，只能操作状态为【待处理】的处罚订单。",
            ]
        ),
        "可组合/被引用关系": "承接 S-FLOW-069；撤销结束子单处罚驳回分支。",
        "自动化状态": "已自动化",
        "备注": "2026-06-24新增：撤销结束子单处罚追缴驳回分支。",
    },
]


NEW_FLOWS = [
    {
        "链路编号": "FLOW-042",
        "链路名称": "同意撤销后-撤销结束子单处罚同意",
        "链路类型": "撤销结束 + 处罚追缴",
        "起点状态": "S-FLOW-016 完成；目标子单撤销结束并展示处罚入口；顶部主单/子单汇总状态仅记录实际展示",
        "终点状态": "丸友集同意处罚/赔偿，处罚处理完成并产生对应金额影响",
        "用例编号序列": "S-FLOW-016 -> S-FLOW-069 -> S-FLOW-070",
        "用例链路（编号+标题）": "S-FLOW-016 丸掌柜同意撤销 -> S-FLOW-069 【丸掌柜】子单撤销结束状态-对撤销结束子单发起处罚 -> S-FLOW-070 【丸友集】撤销结束子单处罚追缴-同意处罚/赔偿",
        "涉及模块": "丸掌柜处理撤销 / 丸掌柜发起处罚追缴 / 处罚追缴-丸友集处理",
        "适用场景": "丸掌柜同意撤销后，对撤销结束子单继续追责处罚，丸友集同意赔偿。",
        "数据复用建议": "可复用已完成 S-FLOW-016 且目标子单展示处罚入口的订单；同意赔偿会产生金额影响，避免同一子单重复生效。",
        "注意事项/缺口": "该分支与 S-FLOW-068 继续发单并列，均承接 S-FLOW-016；发起处罚后必须继续覆盖同意/拒绝结果。",
    },
    {
        "链路编号": "FLOW-043",
        "链路名称": "同意撤销后-撤销结束子单处罚拒绝",
        "链路类型": "撤销结束 + 处罚追缴",
        "起点状态": "S-FLOW-016 完成；目标子单撤销结束并展示处罚入口；顶部主单/子单汇总状态仅记录实际展示",
        "终点状态": "丸友集拒绝处罚/驳回申诉，处罚记录被驳回且不产生赔偿扣减",
        "用例编号序列": "S-FLOW-016 -> S-FLOW-069 -> S-FLOW-071",
        "用例链路（编号+标题）": "S-FLOW-016 丸掌柜同意撤销 -> S-FLOW-069 【丸掌柜】子单撤销结束状态-对撤销结束子单发起处罚 -> S-FLOW-071 【丸友集】撤销结束子单处罚追缴-拒绝处罚/驳回申诉",
        "涉及模块": "丸掌柜处理撤销 / 丸掌柜发起处罚追缴 / 处罚追缴-丸友集处理",
        "适用场景": "丸掌柜同意撤销后，对撤销结束子单继续追责处罚，丸友集拒绝处罚或驳回申诉。",
        "数据复用建议": "需要与同意处罚分支分开使用数据；拒绝分支不应产生赔偿扣减流水。",
        "注意事项/缺口": "该分支与 S-FLOW-068 继续发单并列，均承接 S-FLOW-016；发起处罚后必须继续覆盖同意/拒绝结果。",
    },
]


def headers(ws) -> dict[str, int]:
    return {str(cell.value).strip(): cell.column for cell in ws[1] if cell.value}


def find_row_by_value(ws, col: int, value: str) -> int | None:
    for row in range(2, ws.max_row + 1):
        if str(ws.cell(row, col).value or "").strip() == value:
            return row
    return None


def delete_rows_by_values(ws, col: int, values: set[str]) -> None:
    for row in range(ws.max_row, 1, -1):
        if str(ws.cell(row, col).value or "").strip() in values:
            ws.delete_rows(row, 1)


def copy_row_style_without_fill(ws, source_row: int, target_row: int) -> None:
    ws.row_dimensions[target_row].height = ws.row_dimensions[source_row].height
    for col in range(1, ws.max_column + 1):
        source = ws.cell(source_row, col)
        target = ws.cell(target_row, col)
        if source.has_style:
            target._style = copy.copy(source._style)
        target.number_format = source.number_format
        target.alignment = copy.copy(source.alignment)
        target.font = copy.copy(source.font)
        target.fill = PatternFill(fill_type=None)
        target.border = copy.copy(source.border)


def backup(path: Path, suffix: str) -> None:
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    target = BACKUP_DIR / f"{path.stem}.before_{suffix}_{datetime.now():%Y%m%d-%H%M%S}{path.suffix}"
    shutil.copy2(path, target)
    print(f"backup: {target}")


def upsert_json_cases() -> None:
    for path in JSON_PATHS:
        if not path.exists():
            continue
        data = json.loads(path.read_text(encoding="utf-8"))
        by_id = {case.get("用例编号"): case for case in NEW_CASES}
        existing_ids = {case.get("用例编号") for case in data}
        for item in data:
            case = by_id.get(item.get("用例编号"))
            if case:
                item.update(case)
        insert_index = next((i + 1 for i, case in enumerate(data) if case.get("用例编号") == "S-FLOW-068"), len(data))
        for case in reversed(NEW_CASES):
            if case["用例编号"] not in existing_ids:
                data.insert(insert_index, case)
        path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
        print(f"updated: {path}")


def upsert_case_rows(wb, sheet_name: str, insert_after_case_id: str) -> None:
    ws = wb[sheet_name]
    h = headers(ws)
    id_col = h["用例编号"]
    delete_rows_by_values(ws, id_col, {case["用例编号"] for case in NEW_CASES})
    source_row = find_row_by_value(ws, id_col, insert_after_case_id) or 2
    insert_after = source_row
    for case in NEW_CASES:
        insert_after += 1
        ws.insert_rows(insert_after, 1)
        copy_row_style_without_fill(ws, source_row, insert_after)
        for field, value in case.items():
            col = h.get(field)
            if col:
                ws.cell(insert_after, col).value = value


def upsert_flow_rows(wb) -> None:
    ws = wb["链路流程线"]
    h = headers(ws)
    delete_rows_by_values(ws, h["链路编号"], {flow["链路编号"] for flow in NEW_FLOWS})
    source_row = find_row_by_value(ws, h["链路编号"], "FLOW-008") or 2
    insert_after = source_row
    for flow in NEW_FLOWS:
        insert_after += 1
        ws.insert_rows(insert_after, 1)
        copy_row_style_without_fill(ws, source_row, insert_after)
        for field, value in flow.items():
            col = h.get(field)
            if col:
                ws.cell(insert_after, col).value = value


def upsert_chain_detail_rows(wb) -> None:
    ws = wb["链路步骤明细"]
    h = headers(ws)
    delete_rows_by_values(ws, h["链路编号"], {flow["链路编号"] for flow in NEW_FLOWS})
    case_rows: dict[str, dict[str, object]] = {}
    for row in range(2, ws.max_row + 1):
        case_id = str(ws.cell(row, h["用例编号"]).value or "")
        if case_id in {"S-FLOW-016", *[case["用例编号"] for case in NEW_CASES]}:
            case_rows[case_id] = {
                "用例编号": case_id,
                "所属模块": ws.cell(row, h["所属模块"]).value,
                "用例标题": ws.cell(row, h["用例标题"]).value,
                "前置条件": ws.cell(row, h["前置条件"]).value,
                "预期结果": ws.cell(row, h["预期结果"]).value,
            }
    for case in NEW_CASES:
        case_rows[case["用例编号"]] = case
    rows = [
        ("FLOW-042", 1, case_rows.get("S-FLOW-016")),
        ("FLOW-042", 2, case_rows.get("S-FLOW-069")),
        ("FLOW-042", 3, case_rows.get("S-FLOW-070")),
        ("FLOW-043", 1, case_rows.get("S-FLOW-016")),
        ("FLOW-043", 2, case_rows.get("S-FLOW-069")),
        ("FLOW-043", 3, case_rows.get("S-FLOW-071")),
    ]
    source_row = find_row_by_value(ws, h["用例编号"], "S-FLOW-016") or 2
    insert_after = source_row
    for flow_id, step_no, case in rows:
        if not case:
            continue
        insert_after += 1
        ws.insert_rows(insert_after, 1)
        copy_row_style_without_fill(ws, source_row, insert_after)
        ws.cell(insert_after, h["链路编号"]).value = flow_id
        ws.cell(insert_after, h["步骤序号"]).value = step_no
        ws.cell(insert_after, h["用例编号"]).value = case["用例编号"]
        ws.cell(insert_after, h["所属模块"]).value = case["所属模块"]
        ws.cell(insert_after, h["用例标题"]).value = case["用例标题"]
        ws.cell(insert_after, h["前置条件"]).value = case["前置条件"]
        ws.cell(insert_after, h["预期结果"]).value = case["预期结果"]


def update_excel_files() -> None:
    backup(FLOW_EXCEL, "revoke_end_penalty_cases")
    wb = load_workbook(FLOW_EXCEL)
    upsert_case_rows(wb, "原始用例索引", "S-FLOW-068")
    upsert_case_rows(wb, "用例覆盖索引", "S-FLOW-068")
    upsert_case_rows(wb, "链路步骤明细", "S-FLOW-068")
    upsert_flow_rows(wb)
    upsert_chain_detail_rows(wb)
    wb.save(FLOW_EXCEL)
    print(f"updated: {FLOW_EXCEL}")

    backup(SINGLE_EXCEL, "revoke_end_penalty_cases")
    wb = load_workbook(SINGLE_EXCEL)
    upsert_case_rows(wb, "订单状态流转", "S-FLOW-068")
    if "用例测试列表" in wb.sheetnames:
        ws = wb["用例测试列表"]
        h = headers(ws)
        if "用例编号" in h:
            delete_rows_by_values(ws, h["用例编号"], {case["用例编号"] for case in NEW_CASES})
    wb.save(SINGLE_EXCEL)
    print(f"updated: {SINGLE_EXCEL}")


def main() -> None:
    upsert_json_cases()
    update_excel_files()


if __name__ == "__main__":
    main()
