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_row(
    case_id: str,
    module: str,
    title: str,
    precondition: str,
    data: str,
    action_steps: list[str],
    expected: str,
    assertion: str,
    relation: str,
    remark: str = "",
) -> list[str]:
    return [
        case_id,
        "订单状态流转用例",
        module,
        title,
        "丸友集：工作室主账号；丸掌柜：企业主账号；打手应用：打手账号。",
        precondition,
        data,
        steps(action_steps),
        expected,
        assertion,
        relation,
        "待自动化",
        remark,
    ]


CASE_UPDATES = {
    "S-FLOW-072": status_row(
        "S-FLOW-072",
        "处罚追缴-丸友集发起",
        "【丸友集】已完成/撤销完成子单-打开处罚追缴并选择处理类型",
        "丸友集工作室履约应用-订单列表存在子单状态为已完成或撤销完成的订单；目标子单操作列展示【处罚追缴】入口。",
        "主单号、来源单号、子单号、订单标题、打手账号、处理类型（追缴单/处罚单）。",
        [
            "登录丸友集工作室主账号。",
            "进入丸友集-工作室履约应用-订单列表。",
            "按主单号、来源单号或订单标题搜索目标订单。",
            "打开订单详情或子单列表，定位状态为已完成或撤销完成的目标子单。",
            "点击目标子单操作列的【处罚追缴】。",
            "确认处罚追缴弹框展示【追缴单】和【处罚单】两种处理类型。",
            "根据后续分支选择处理类型：追缴单分支接 S-FLOW-073，处罚单分支接 S-FLOW-074。",
        ],
        "处罚追缴弹框正常打开，且可选择追缴单或处罚单；此用例作为两个分支的公共入口，不单独提交。",
        "目标子单状态必须为已完成或撤销完成；弹框类型选项、主单号/子单号/打手信息与订单详情一致；后续由 S-FLOW-073 或 S-FLOW-074 完成提交和结果校验。",
        "承接 FLOW-012 或 FLOW-046；后续分支为 S-FLOW-073/S-FLOW-074。",
        "2026-06-29 调整：072 作为处罚追缴公共入口，073/074 接在 072 后面形成分支。",
    ),
    "S-FLOW-073": status_row(
        "S-FLOW-073",
        "处罚追缴-追缴流程",
        "【丸友集/打手】处罚追缴-选择追缴单并由打手完成追缴",
        "已执行 S-FLOW-072 并打开处罚追缴弹框；选择【追缴单】分支；目标打手账号可登录打手应用-我的处罚。",
        "主单号、来源单号、子单号、订单标题、打手账号、追缴原因、追缴内容、追缴时限、处罚金额、证据图片、完成说明。",
        [
            "在 S-FLOW-072 打开的处罚追缴弹框中选择【追缴单】。",
            "勾选追缴原因，填写追缴内容、追缴时限、处罚金额，并按页面要求上传证据图片。",
            "提交追缴单。",
            "登录目标打手账号。",
            "进入打手应用-我的处罚。",
            "切换到【追缴单】页签。",
            "按处罚/追缴单号、子单号、主单号或来源单号查询新生成的追缴单。",
            "点击操作列【查看】。",
            "在追缴单详情中核对追缴原因、追缴内容、追缴时限和处罚金额。",
            "点击【提交完成】或页面等价完成入口。",
            "填写完成说明，并按页面要求上传完成证据图片。",
            "提交完成追缴并刷新回查状态。",
        ],
        "丸友集侧成功生成追缴单；打手侧在我的处罚-追缴单完成追缴；追缴单状态进入已完成、待确认或页面实际完成态。",
        "丸友集侧追缴单、打手我的处罚追缴单的主单号/子单号/打手账号/追缴金额一致；原订单子单终态不应被追缴动作错误回退。",
        "承接 S-FLOW-072；被 FLOW-013 引用。",
        "追缴单需要打手继续完成追缴内容。",
    ),
    "S-FLOW-074": status_row(
        "S-FLOW-074",
        "处罚追缴-处罚流程",
        "【丸友集/打手】处罚追缴-选择处罚单并在打手侧查看处罚金额",
        "已执行 S-FLOW-072 并打开处罚追缴弹框；选择【处罚单】分支；目标打手账号可登录打手应用-我的处罚。",
        "主单号、来源单号、子单号、订单标题、打手账号、处罚原因、处罚金额、证据图片。",
        [
            "在 S-FLOW-072 打开的处罚追缴弹框中选择【处罚单】。",
            "勾选处罚原因，填写处罚金额，并按页面要求上传证据图片或补充说明。",
            "提交处罚单。",
            "登录目标打手账号。",
            "进入打手应用-我的处罚。",
            "切换到【罚单】页签。",
            "按子单号、主单号或处罚单号查询新生成的处罚单。",
            "点击操作列【查看】。",
            "核对处罚金额和处罚状态。",
        ],
        "丸友集侧成功生成处罚单；打手应用-我的处罚-罚单页签出现对应处罚单，状态为已处罚或页面实际生效状态；处罚金额与丸友集提交金额一致。",
        "处罚单为直接处罚金额，不要求打手继续完成追缴任务；打手侧处罚金额、主单号、子单号、打手账号与丸友集侧一致。",
        "承接 S-FLOW-072；被 FLOW-014 引用。",
        "处罚单前半段复用处罚追缴入口，结果以打手我的处罚查看处罚金额为准。",
    ),
}


FLOWS = {
    "FLOW-013": [
        "FLOW-013",
        "已发打手待接单-同意撤销后撤销完成子单-处罚追缴追缴单流程",
        "撤销完成 + 处罚追缴-追缴单",
        "已执行 FLOW-012；丸掌柜同意撤销后，丸友集侧目标子单状态为撤销完成",
        "追缴单由打手完成追缴",
        "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-075 -> S-FLOW-076 -> S-FLOW-072 -> S-FLOW-073",
        "S-FLOW-007(创建主单已推单) -> S-FLOW-014(丸友集确认接单) -> S-FLOW-021(丸友集立即发单到打手) -> S-FLOW-075(丸友集申请撤销此单) -> S-FLOW-076(丸掌柜同意撤销，丸友集子单撤销完成) -> S-FLOW-072(打开处罚追缴并选择处理类型) -> S-FLOW-073(选择追缴单并由打手完成追缴)",
        "上游发单 -> 丸友集申请撤销 -> 丸掌柜处理撤销 -> 处罚追缴-追缴流程",
        "替换原打手未接单阶段主单售后正向链路，验证撤销完成子单进入处罚追缴后，追缴单由打手完成。",
        "复用 FLOW-012 的同意撤销结果；追缴单分支需要打手账号完成追缴。",
        "2026-06-29 按用户反馈用处罚追缴追缴单链路替换原 FLOW-013 主单售后链路。",
    ],
    "FLOW-014": [
        "FLOW-014",
        "已发打手待接单-拒绝撤销后已完成子单-处罚追缴处罚单流程",
        "已完成 + 处罚追缴-处罚单",
        "已执行 FLOW-046；丸掌柜拒绝撤销后，丸友集侧目标子单状态为已完成",
        "处罚单生成并在打手侧可查看处罚金额",
        "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-021 -> S-FLOW-075 -> S-FLOW-077 -> S-FLOW-072 -> S-FLOW-074",
        "S-FLOW-007(创建主单已推单) -> S-FLOW-014(丸友集确认接单) -> S-FLOW-021(丸友集立即发单到打手) -> S-FLOW-075(丸友集申请撤销此单) -> S-FLOW-077(丸掌柜拒绝撤销，丸友集子单已完成) -> S-FLOW-072(打开处罚追缴并选择处理类型) -> S-FLOW-074(选择处罚单并在打手侧查看处罚金额)",
        "上游发单 -> 丸友集申请撤销 -> 丸掌柜处理撤销 -> 处罚追缴-处罚流程",
        "替换原打手未接单阶段主单售后取消链路，验证已完成子单进入处罚追缴后，处罚单金额可在打手我的处罚中查看。",
        "复用 FLOW-046 的拒绝撤销结果；处罚单分支不要求打手继续完成追缴任务。",
        "2026-06-29 按用户反馈用处罚追缴处罚单链路替换原 FLOW-014 主单售后链路。",
    ],
}


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


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


def copy_style(ws, source: int, target: int) -> None:
    for column in range(1, ws.max_column + 1):
        src = ws.cell(source, column)
        dst = ws.cell(target, column)
        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


def write_row(ws, row: int, values: list[object]) -> None:
    for column, value in enumerate(values, start=1):
        ws.cell(row, column).value = value
        ws.cell(row, column).alignment = copy(ws.cell(row, column).alignment)
        ws.cell(row, column).alignment = ws.cell(row, column).alignment.copy(wrap_text=True, vertical="top")


def upsert(ws, key: str, values: list[object], after: str | None = None) -> None:
    row = find_row(ws, key)
    if row:
        write_row(ws, row, values)
        return
    if after and (anchor := find_row(ws, after)):
        row = anchor + 1
        ws.insert_rows(row)
        copy_style(ws, anchor, row)
        write_row(ws, row, values)
        return
    ws.append(values)
    copy_style(ws, max(2, ws.max_row - 1), ws.max_row)


def delete_flow_steps(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 move_after(ws, key: str, anchor: str) -> None:
    source = find_row(ws, key)
    anchor_row = find_row(ws, anchor)
    if source is None or anchor_row is None or source == anchor_row + 1:
        return
    values = [ws.cell(source, column).value for column in range(1, ws.max_column + 1)]
    ws.delete_rows(source)
    if source < anchor_row:
        anchor_row -= 1
    target = anchor_row + 1
    ws.insert_rows(target)
    copy_style(ws, anchor_row, target)
    write_row(ws, target, values)


def update_main() -> None:
    backup(MAIN_WORKBOOK)
    workbook = load_workbook(MAIN_WORKBOOK)
    ws = workbook["订单状态流转"]
    for case_id in ["S-FLOW-072", "S-FLOW-073", "S-FLOW-074"]:
        upsert(ws, case_id, CASE_UPDATES[case_id])
    workbook.save(MAIN_WORKBOOK)
    print(f"updated {MAIN_WORKBOOK}")


def raw_meta(raw_ws, case_id: str) -> tuple[str, str, str, str]:
    if case_id in CASE_UPDATES:
        row = CASE_UPDATES[case_id]
        return row[2], row[3], row[5], row[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 replace_link_list(value: str, remove: set[str], add: list[str]) -> str:
    parts = [part for part in str(value or "").replace(",", "、").split("、") if part]
    parts = [part for part in parts if part not in remove]
    for item in add:
        if item not in parts:
            parts.append(item)
    return "、".join(parts)


def update_coverage(coverage_ws) -> None:
    remove_old = {"FLOW-013", "FLOW-014"}
    old_cases = ["S-FLOW-023", "S-FLOW-024", "S-FLOW-025", "S-FLOW-003", "S-FLOW-051"]
    for case_id in old_cases:
        row = find_row(coverage_ws, case_id)
        if not row:
            continue
        links = replace_link_list(str(coverage_ws.cell(row, 5).value or ""), remove_old, [])
        coverage_ws.cell(row, 5).value = links
        coverage_ws.cell(row, 4).value = len([part for part in links.split("、") if part])
        coverage_ws.cell(row, 7).value = "FLOW-013/014 已替换为处罚追缴分支，不再引用该售后节点。"

    updates = {
        "S-FLOW-072": ("处罚追缴-丸友集发起", CASE_UPDATES["S-FLOW-072"][3], ["FLOW-012", "FLOW-046", "FLOW-013", "FLOW-014"], "处罚追缴公共入口，后接 073/074。"),
        "S-FLOW-073": ("处罚追缴-追缴流程", CASE_UPDATES["S-FLOW-073"][3], ["FLOW-013"], "追缴单分支，接在 S-FLOW-072 后。"),
        "S-FLOW-074": ("处罚追缴-处罚流程", CASE_UPDATES["S-FLOW-074"][3], ["FLOW-014"], "处罚单分支，接在 S-FLOW-072 后。"),
        "S-FLOW-075": ("丸友集申请撤销", "丸友集已发单到打手抢单大厅/打手未接单阶段-丸友集申请撤销此单", ["FLOW-012", "FLOW-046", "FLOW-013", "FLOW-014"], "同意/拒绝撤销及其处罚追缴后续链路共用。"),
        "S-FLOW-076": ("丸掌柜处理撤销", "【丸掌柜】同意丸友集已发打手未接单订单撤销-丸友集子单撤销完成", ["FLOW-012", "FLOW-013"], "同意撤销后可走追缴单完整链路。"),
        "S-FLOW-077": ("丸掌柜处理撤销", "【丸掌柜】拒绝丸友集已发打手未接单订单撤销-丸友集子单已完成", ["FLOW-046", "FLOW-014"], "拒绝撤销后可走处罚单完整链路。"),
    }
    for case_id, (module, title, links, note) in updates.items():
        row = find_row(coverage_ws, case_id)
        values = [case_id, module, title, len(links), "、".join(links), "是", note]
        upsert(coverage_ws, case_id, values, "S-FLOW-074")


def update_flow() -> None:
    backup(FLOW_WORKBOOK)
    workbook = load_workbook(FLOW_WORKBOOK)
    summary = workbook["链路流程线"]
    details = workbook["链路步骤明细"]
    raw = workbook["原始用例索引"]
    coverage = workbook["用例覆盖索引"]
    notes = workbook["说明"]

    for case_id in ["S-FLOW-072", "S-FLOW-073", "S-FLOW-074"]:
        row = CASE_UPDATES[case_id]
        upsert(raw, case_id, [case_id, row[2], row[3], row[5], row[8], row[7]], "S-FLOW-077")

    upsert(summary, "FLOW-013", FLOWS["FLOW-013"])
    upsert(summary, "FLOW-014", FLOWS["FLOW-014"])
    move_after(summary, "FLOW-013", "FLOW-046")
    move_after(summary, "FLOW-014", "FLOW-013")

    delete_flow_steps(details, {"FLOW-013", "FLOW-014"})
    flow_cases = {
        "FLOW-013": ["S-FLOW-007", "S-FLOW-014", "S-FLOW-021", "S-FLOW-075", "S-FLOW-076", "S-FLOW-072", "S-FLOW-073"],
        "FLOW-014": ["S-FLOW-007", "S-FLOW-014", "S-FLOW-021", "S-FLOW-075", "S-FLOW-077", "S-FLOW-072", "S-FLOW-074"],
    }
    for flow_id, case_ids in flow_cases.items():
        for index, case_id in enumerate(case_ids, start=1):
            module, title, precondition, expected = raw_meta(raw, case_id)
            details.append([flow_id, index, case_id, module, title, precondition, expected])
            copy_style(details, max(2, details.max_row - 1), details.max_row)

    update_coverage(coverage)
    notes.append(["更新记录", f"{datetime.now():%Y-%m-%d %H:%M:%S} 用处罚追缴追缴单/处罚单链路替换 FLOW-013/FLOW-014，并将 S-FLOW-073/074 接到 S-FLOW-072 后。"])
    copy_style(notes, max(2, notes.max_row - 1), notes.max_row)
    workbook.save(FLOW_WORKBOOK)
    print(f"updated {FLOW_WORKBOOK}")


def main() -> None:
    update_main()
    update_flow()


if __name__ == "__main__":
    main()
