from __future__ import annotations

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

from openpyxl import load_workbook


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


def steps(items: list[str]) -> str:
    return "\n".join(f"{index}. {item}" for index, item in enumerate(items, start=1))


def status_case_row(
    case_id: str,
    module: str,
    title: str,
    login: str,
    precondition: str,
    data: str,
    action_steps: list[str],
    expected: str,
    assertion: str,
    relation: str,
    status: str = "待自动化",
    remark: str = "",
) -> list[str]:
    return [
        case_id,
        "订单状态流转用例",
        module,
        title,
        login,
        precondition,
        data,
        steps(action_steps),
        expected,
        assertion,
        relation,
        status,
        remark,
    ]


S_FLOW_022 = status_case_row(
    "S-FLOW-022",
    "丸友集申请撤销",
    "丸友集已发单到打手抢单大厅/打手未接单阶段-丸友集申请撤销此单",
    "丸友集：工作室主账号；丸掌柜：企业主账号；打手应用：打手账号。",
    "已执行 S-FLOW-021，丸友集已将订单发到打手抢单大厅，打手尚未接单；页面不支持丸友集直接撤回发单，只能由丸友集发起撤销申请，等待丸掌柜处理。",
    "主单号、来源单号、子单号、订单标题、打手账号、撤销原因、撤销凭证。",
    [
        "登录丸友集工作室主账号。",
        "进入丸友集-工作室履约应用-订单列表。",
        "按主单号、来源单号或订单标题搜索目标订单。",
        "打开订单详情或子单列表，确认打手子单已发出且打手未接单。",
        "点击【申请撤销】、【撤销订单】、【向上撤销】或页面等价撤销入口。",
        "填写撤销原因，并按页面要求上传撤销凭证。",
        "提交撤销申请。",
        "登录丸掌柜主账号，进入商家管理-订单管理。",
        "搜索同一订单，进入订单详情，确认可看到丸友集撤销申请及处理撤销入口。",
    ],
    "丸友集撤销申请提交成功；丸掌柜侧订单详情出现待处理撤销申请/处理撤销入口；该阶段不应出现丸友集直接撤回发单成功或重新发单结果。",
    "丸友集侧订单进入撤销中/待上游处理等页面实际待处理状态；丸掌柜侧主单号、来源单号、子单号、撤销原因与丸友集侧一致；打手侧仍不应产生已接单履约状态。",
    "承接 S-FLOW-021；后续分支为 S-FLOW-075（丸掌柜同意撤销）或 S-FLOW-076（丸掌柜拒绝撤销）。",
    remark="2026-06-29 修正：S-FLOW-021 后不能撤回发单，只能发起撤销并由丸掌柜处理。",
)


NEW_CASES = {
    "S-FLOW-075": status_case_row(
        "S-FLOW-075",
        "丸掌柜处理撤销",
        "【丸掌柜】同意丸友集已发打手未接单订单撤销-丸友集子单撤销完成",
        "丸掌柜：企业主账号；丸友集：工作室主账号；打手应用：打手账号。",
        "已执行 S-FLOW-022，丸友集已对已发打手未接单订单提交撤销申请；丸掌柜订单详情存在待处理撤销申请。",
        "主单号、来源单号、子单号、订单标题、撤销原因、处理备注。",
        [
            "登录丸掌柜主账号。",
            "进入丸掌柜-商家管理-订单管理。",
            "按主单号、来源单号或订单标题搜索目标订单。",
            "进入订单详情，定位丸友集撤销申请或子单列表中的撤销中记录。",
            "点击【处理撤销】、【撤销审核】或页面等价处理入口。",
            "选择【同意撤销】。",
            "填写处理备注并确认提交。",
            "登录丸友集工作室主账号。",
            "进入工作室履约应用-订单列表，搜索同一订单。",
            "打开订单详情或子单列表，回查目标子单状态。",
        ],
        "丸掌柜同意撤销成功；丸友集侧目标子单状态变为撤销完成；该终态子单可继续衔接【处罚追缴】S-FLOW-072。",
        "丸掌柜处理结果、丸友集子单撤销完成状态、主单号/来源单号/子单号一致；原打手侧不应继续展示可接单或已接单履约入口；后续可以对撤销完成子单执行处罚追缴。",
        "承接 S-FLOW-022；被 FLOW-012 引用；后续可接 S-FLOW-072。",
        remark="用户确认：同意撤销后丸友集侧子单状态为撤销完成，可接处罚追缴。",
    ),
    "S-FLOW-076": status_case_row(
        "S-FLOW-076",
        "丸掌柜处理撤销",
        "【丸掌柜】拒绝丸友集已发打手未接单订单撤销-丸友集子单已完成",
        "丸掌柜：企业主账号；丸友集：工作室主账号；打手应用：打手账号。",
        "已执行 S-FLOW-022，丸友集已对已发打手未接单订单提交撤销申请；丸掌柜订单详情存在待处理撤销申请。",
        "主单号、来源单号、子单号、订单标题、撤销原因、拒绝原因、处理备注。",
        [
            "登录丸掌柜主账号。",
            "进入丸掌柜-商家管理-订单管理。",
            "按主单号、来源单号或订单标题搜索目标订单。",
            "进入订单详情，定位丸友集撤销申请或子单列表中的撤销中记录。",
            "点击【处理撤销】、【撤销审核】或页面等价处理入口。",
            "选择【拒绝撤销】或【驳回撤销】。",
            "填写拒绝原因/处理备注并确认提交。",
            "登录丸友集工作室主账号。",
            "进入工作室履约应用-订单列表，搜索同一订单。",
            "打开订单详情或子单列表，回查目标子单状态。",
        ],
        "丸掌柜拒绝撤销成功；丸友集侧目标子单状态变为已完成；该终态子单可继续衔接【处罚追缴】S-FLOW-072。",
        "丸掌柜拒绝处理结果、丸友集子单已完成状态、主单号/来源单号/子单号一致；拒绝撤销不应回到撤回发单或重新发单分支；后续可以对已完成子单执行处罚追缴。",
        "承接 S-FLOW-022；被 FLOW-046 引用；后续可接 S-FLOW-072。",
        remark="用户确认：拒绝撤销后丸友集侧子单状态变为已完成，可接处罚追缴。",
    ),
}


FLOW_UPDATES = {
    "FLOW-012": [
        "FLOW-012",
        "已发打手待接单-丸友集申请撤销-丸掌柜同意撤销-处罚追缴",
        "已发打手未接单撤销同意 + 处罚追缴",
        "已执行 S-FLOW-021；丸友集已发单到打手抢单大厅且打手未接单；不能直接撤回发单",
        "丸掌柜同意撤销；丸友集侧子单撤销完成；可进入处罚追缴",
        "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-022 -> S-FLOW-075 -> S-FLOW-072",
        "S-FLOW-007(【丸掌柜】主单待接单状态/子单待接单-已推单状态-创建主单已推单) -> S-FLOW-014(丸友集抢单大厅待接单阶段-丸友集确认接单) -> S-FLOW-021(丸友集已接单/未发单到打手抢单大厅阶段-立即发单) -> S-FLOW-022(丸友集已发单到打手抢单大厅/打手未接单阶段-丸友集申请撤销此单) -> S-FLOW-075(【丸掌柜】同意丸友集已发打手未接单订单撤销-丸友集子单撤销完成) -> S-FLOW-072(【丸友集】已完成/撤销完成子单-发起追缴单)",
        "上游发单 -> 丸友集申请撤销 -> 丸掌柜处理撤销 -> 处罚追缴-丸友集发起",
        "验证丸友集发给打手后、打手未接单前不可撤回发单，只能发起撤销；丸掌柜同意后丸友集侧子单为撤销完成，并可继续处罚追缴。",
        "建议与拒绝撤销分支分开使用订单；同意撤销后的终态子单可作为 S-FLOW-072 的撤销完成前置数据。",
        "2026-06-29 修正：移除错误的 S-FLOW-022 撤回发单和 S-FLOW-021 重新发单衔接。",
    ],
    "FLOW-046": [
        "FLOW-046",
        "已发打手待接单-丸友集申请撤销-丸掌柜拒绝撤销-处罚追缴",
        "已发打手未接单撤销拒绝 + 处罚追缴",
        "已执行 S-FLOW-021；丸友集已发单到打手抢单大厅且打手未接单；不能直接撤回发单",
        "丸掌柜拒绝撤销；丸友集侧子单已完成；可进入处罚追缴",
        "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-022 -> S-FLOW-076 -> S-FLOW-072",
        "S-FLOW-007(【丸掌柜】主单待接单状态/子单待接单-已推单状态-创建主单已推单) -> S-FLOW-014(丸友集抢单大厅待接单阶段-丸友集确认接单) -> S-FLOW-021(丸友集已接单/未发单到打手抢单大厅阶段-立即发单) -> S-FLOW-022(丸友集已发单到打手抢单大厅/打手未接单阶段-丸友集申请撤销此单) -> S-FLOW-076(【丸掌柜】拒绝丸友集已发打手未接单订单撤销-丸友集子单已完成) -> S-FLOW-072(【丸友集】已完成/撤销完成子单-发起追缴单)",
        "上游发单 -> 丸友集申请撤销 -> 丸掌柜处理撤销 -> 处罚追缴-丸友集发起",
        "验证丸友集发给打手后、打手未接单前不可撤回发单，只能发起撤销；丸掌柜拒绝后丸友集侧子单为已完成，并可继续处罚追缴。",
        "建议与同意撤销分支分开使用订单；拒绝撤销后的终态子单可作为 S-FLOW-072 的已完成前置数据。",
        "2026-06-29 新增拒绝撤销分支，按用户确认拒绝后丸友集侧子单状态变为已完成。",
    ],
}


def copy_row_style(ws, src_row: int, dst_row: int) -> None:
    if src_row < 1 or src_row == dst_row:
        return
    for col in range(1, ws.max_column + 1):
        src = ws.cell(src_row, col)
        dst = ws.cell(dst_row, col)
        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
    ws.row_dimensions[dst_row].height = ws.row_dimensions[src_row].height


def set_wrapped_top(ws, row: int, max_col: int) -> None:
    for column in range(1, max_col + 1):
        old = ws.cell(row, column).alignment
        ws.cell(row, column).alignment = copy(old)
        ws.cell(row, column).alignment = ws.cell(row, column).alignment.copy(wrap_text=True, vertical="top")


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


def write_values(ws, row: int, values: list[object]) -> None:
    for column, value in enumerate(values, start=1):
        ws.cell(row, column).value = value
    set_wrapped_top(ws, row, len(values))


def upsert_after_anchor(ws, key: str, values: list[object], anchor: str | None = None) -> None:
    row = find_row(ws, key)
    if row:
        write_values(ws, row, values)
        return
    if anchor:
        anchor_row = find_row(ws, anchor)
        if anchor_row:
            row = anchor_row + 1
            ws.insert_rows(row)
            copy_row_style(ws, anchor_row, row)
            write_values(ws, row, values)
            return
    ws.append(values)
    copy_row_style(ws, max(2, ws.max_row - 1), ws.max_row)
    set_wrapped_top(ws, ws.max_row, len(values))


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


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


def update_main_workbook() -> None:
    backup(MAIN_WORKBOOK)
    workbook = load_workbook(MAIN_WORKBOOK)
    status_ws = workbook["订单状态流转"]
    upsert_after_anchor(status_ws, "S-FLOW-022", S_FLOW_022)
    upsert_after_anchor(status_ws, "S-FLOW-075", NEW_CASES["S-FLOW-075"], "S-FLOW-074")
    upsert_after_anchor(status_ws, "S-FLOW-076", NEW_CASES["S-FLOW-076"], "S-FLOW-075")
    workbook.save(MAIN_WORKBOOK)
    print(f"updated {MAIN_WORKBOOK}")


def flow_case_ids(flow_id: str) -> list[str]:
    return ["S-FLOW-007", "S-FLOW-014", "S-FLOW-021", "S-FLOW-022", "S-FLOW-075", "S-FLOW-072"] if flow_id == "FLOW-012" else [
        "S-FLOW-007",
        "S-FLOW-014",
        "S-FLOW-021",
        "S-FLOW-022",
        "S-FLOW-076",
        "S-FLOW-072",
    ]


def case_meta(case_id: str, raw_ws) -> tuple[str, str, str, str]:
    if case_id == "S-FLOW-022":
        return S_FLOW_022[2], S_FLOW_022[3], S_FLOW_022[5], S_FLOW_022[8]
    if case_id in NEW_CASES:
        case = NEW_CASES[case_id]
        return case[2], case[3], case[5], case[8]
    for row in range(2, raw_ws.max_row + 1):
        if str(raw_ws.cell(row, 1).value or "").strip() == case_id:
            return (
                str(raw_ws.cell(row, 2).value or ""),
                str(raw_ws.cell(row, 3).value or ""),
                str(raw_ws.cell(row, 4).value or ""),
                str(raw_ws.cell(row, 5).value or ""),
            )
    return "", "", "", ""


def update_coverage_links(coverage_ws) -> None:
    affected = {
        "S-FLOW-021": ("FLOW-012", "FLOW-046"),
        "S-FLOW-022": ("FLOW-012", "FLOW-046"),
        "S-FLOW-072": ("FLOW-044", "FLOW-012", "FLOW-046"),
        "S-FLOW-075": ("FLOW-012",),
        "S-FLOW-076": ("FLOW-046",),
    }
    case_titles = {
        "S-FLOW-022": (S_FLOW_022[2], S_FLOW_022[3]),
        "S-FLOW-075": (NEW_CASES["S-FLOW-075"][2], NEW_CASES["S-FLOW-075"][3]),
        "S-FLOW-076": (NEW_CASES["S-FLOW-076"][2], NEW_CASES["S-FLOW-076"][3]),
    }
    for case_id, links in affected.items():
        row = find_row(coverage_ws, case_id)
        if row is None:
            module, title = case_titles.get(case_id, ("", ""))
            coverage_ws.append([case_id, module, title, len(links), "、".join(links), "是", "FLOW-012 修正后新增/调整覆盖。"])
            copy_row_style(coverage_ws, max(2, coverage_ws.max_row - 1), coverage_ws.max_row)
            set_wrapped_top(coverage_ws, coverage_ws.max_row, 7)
            continue
        existing = str(coverage_ws.cell(row, 5).value or "")
        parts = [part for part in existing.replace(",", "、").split("、") if part]
        parts = [part for part in parts if not (case_id == "S-FLOW-021" and part == "FLOW-012")]
        for link in links:
            if link not in parts:
                parts.append(link)
        if case_id in case_titles:
            coverage_ws.cell(row, 2).value = case_titles[case_id][0]
            coverage_ws.cell(row, 3).value = case_titles[case_id][1]
        coverage_ws.cell(row, 4).value = len(parts)
        coverage_ws.cell(row, 5).value = "、".join(parts)
        coverage_ws.cell(row, 6).value = "是"
        coverage_ws.cell(row, 7).value = "FLOW-012 修正：S-FLOW-021 后改为丸友集申请撤销，丸掌柜同意/拒绝后均可接 S-FLOW-072。"
        set_wrapped_top(coverage_ws, row, 7)


def update_flow_workbook() -> None:
    backup(FLOW_WORKBOOK)
    workbook = load_workbook(FLOW_WORKBOOK)
    summary_ws = workbook["链路流程线"]
    step_ws = workbook["链路步骤明细"]
    coverage_ws = workbook["用例覆盖索引"]
    raw_ws = workbook["原始用例索引"]
    note_ws = workbook["说明"]

    upsert_after_anchor(raw_ws, "S-FLOW-022", ["S-FLOW-022", S_FLOW_022[2], S_FLOW_022[3], S_FLOW_022[5], S_FLOW_022[8], S_FLOW_022[7]])
    upsert_after_anchor(raw_ws, "S-FLOW-075", ["S-FLOW-075", NEW_CASES["S-FLOW-075"][2], NEW_CASES["S-FLOW-075"][3], NEW_CASES["S-FLOW-075"][5], NEW_CASES["S-FLOW-075"][8], NEW_CASES["S-FLOW-075"][7]], "S-FLOW-074")
    upsert_after_anchor(raw_ws, "S-FLOW-076", ["S-FLOW-076", NEW_CASES["S-FLOW-076"][2], NEW_CASES["S-FLOW-076"][3], NEW_CASES["S-FLOW-076"][5], NEW_CASES["S-FLOW-076"][8], NEW_CASES["S-FLOW-076"][7]], "S-FLOW-075")

    upsert_after_anchor(summary_ws, "FLOW-012", FLOW_UPDATES["FLOW-012"])
    upsert_after_anchor(summary_ws, "FLOW-046", FLOW_UPDATES["FLOW-046"], "FLOW-045")

    delete_rows_by_flow(step_ws, {"FLOW-012", "FLOW-046"})
    for flow_id in ["FLOW-012", "FLOW-046"]:
        for index, case_id in enumerate(flow_case_ids(flow_id), start=1):
            module, title, precondition, expected = case_meta(case_id, raw_ws)
            step_ws.append([flow_id, index, case_id, module, title, precondition, expected])
            copy_row_style(step_ws, max(2, step_ws.max_row - 1), step_ws.max_row)
            set_wrapped_top(step_ws, step_ws.max_row, 7)

    update_coverage_links(coverage_ws)
    note_ws.append(["更新记录", f"{datetime.now():%Y-%m-%d %H:%M:%S} 修正 FLOW-012：S-FLOW-021 后不能撤回发单，改为丸友集申请撤销，丸掌柜同意/拒绝后接 S-FLOW-072。"])
    copy_row_style(note_ws, max(2, note_ws.max_row - 1), note_ws.max_row)
    set_wrapped_top(note_ws, note_ws.max_row, 2)
    workbook.save(FLOW_WORKBOOK)
    print(f"updated {FLOW_WORKBOOK}")


def main() -> None:
    update_main_workbook()
    update_flow_workbook()


if __name__ == "__main__":
    main()
