from __future__ import annotations

import json
from copy import copy
from pathlib import Path

from openpyxl import load_workbook


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


S021_UPDATES = {
    "用例编号": "S-FLOW-021",
    "所属模块": "上游发单",
    "用例标题": "【丸友集视角】丸友集已接单未发打手 / 丸掌柜主单服务中 / 子单代练中 - 发单到打手抢单大厅",
    "前置条件": "丸友集已确认接收上游订单，订单在丸友集工作室履约应用-订单列表中，尚未发给打手。",
    "测试数据": "来源单号、订单标题、丸掌柜主单号、丸友集父单号、价格模式=百分比发单价、收单价百分比=90、服务时限、联系电话。",
    "执行步骤": "\n".join(
        [
            "1. 登录丸友集工作室主账号。",
            "2. 进入工作室履约应用-订单列表。",
            "3. 按来源单号或订单标题查询目标订单。",
            "4. 确认订单为丸友集已接单但未发给打手状态，操作区展示立即发单。",
            "5. 点击立即发单。",
            "6. 在发单弹框中选择分配方式为抢单大厅。",
            "7. 选择或确认价格模式为百分比发单价，填写收单价百分比90，并确认订单内容、时间要求、联系电话等必填字段。",
            "8. 点击确认发单。",
            "9. 回到丸友集订单列表，按来源单号或订单标题查询目标订单并记录父单状态和子单状态。",
            "10. 登录丸掌柜账号，进入订单详情记录主单和子单状态。",
        ]
    ),
    "预期结果": "\n".join(
        [
            "1. 丸友集成功将已接单订单发到打手抢单大厅。",
            "2. 打手抢单大厅可按页面规则展示该订单，尚未绑定具体打手。",
            "3. 丸掌柜主单为服务中/代练中等继续履约状态，子单列表展示已发布、已接单、服务中或页面对应发单后状态。",
        ]
    ),
    "跨端/状态断言": "\n".join(
        [
            "1. S-FLOW-021 只验证发到抢单大厅，不承担指派打手发单。",
            "2. 指派打手发单使用 S-FLOW-079，不能通过环境变量偷换 S-FLOW-021 的业务含义。",
        ]
    ),
    "自动化状态": "已自动化",
    "备注": "2026-07-01修正：S-FLOW-021恢复为发到抢单大厅；指派打手发单拆到 S-FLOW-079。",
}

S079_ROW = {
    "用例编号": "S-FLOW-079",
    "用例层级": "订单状态流转用例",
    "来源/组合依据": "whiteboard_exported_image.png + 状态机对应关系梳理.html：【指派单 下游取消订单】",
    "所属模块": "指派单 下游取消订单",
    "用例标题": "【丸友集视角】丸友集已接单未发打手 / 丸掌柜主单服务中 / 子单代练中 - 指派打手发单",
    "登录平台/角色账号": "丸掌柜：企业主账号或客服账号；丸友集：工作室主账号或客服账号；打手：打手子账号。",
    "前置条件": "丸友集已确认接收上游订单，订单在丸友集工作室履约应用-订单列表中；如工作室下没有已启用打手，需先在企业管理-成员管理邀请并确认打手成员。",
    "测试数据": "来源单号、订单标题、丸掌柜主单号、丸友集父单号、目标打手姓名/手机号、价格模式=百分比发单价、收单价百分比=90、服务时限、联系电话。",
    "执行步骤": "\n".join(
        [
            "1. 登录丸友集工作室主账号。",
            "2. 检查当前工作室下是否存在已启用打手成员。",
            "3. 如不存在可用打手，进入企业管理-成员管理，邀请成员并选择丸友集产品、打手角色，完成注册/确认后返回丸友集。",
            "4. 进入工作室履约应用-订单列表。",
            "5. 按来源单号或订单标题查询目标订单。",
            "6. 确认订单为丸友集已接单但未发给打手状态，操作区展示立即发单。",
            "7. 点击立即发单。",
            "8. 在发单弹框中选择分配方式为指派打手。",
            "9. 点击选择打手，按成员名称或手机号查询目标打手。",
            "10. 勾选目标打手并点击确认选择。",
            "11. 选择或确认价格模式为百分比发单价，填写收单价百分比90，并确认订单内容、时间要求、联系电话等必填字段。",
            "12. 点击确认发单。",
            "13. 回到丸友集订单列表，按来源单号或订单标题查询目标订单并记录父单状态、当前打手和子单状态。",
            "14. 登录被指派打手账号，进入打手应用-我的订单或待接订单列表，确认该订单可见且处于待接单/可处理状态。",
            "15. 登录丸掌柜账号，进入订单详情记录主单和子单状态。",
        ]
    ),
    "预期结果": "\n".join(
        [
            "1. 丸友集成功将已接单订单以指派打手方式发给目标打手。",
            "2. 发单弹框回填的打手姓名/手机号与目标打手一致。",
            "3. 被指派打手侧可见该订单，订单处于待接单或页面对应可处理状态。",
            "4. 丸掌柜主单为服务中/代练中等继续履约状态，子单列表展示已发布、已接单、服务中或页面对应发单后状态。",
        ]
    ),
    "跨端/状态断言": "\n".join(
        [
            "1. 指派打手前必须存在该丸友集工作室下的已启用打手；没有则先邀请并确认打手成员。",
            "2. 只有被指派打手可见或处理该订单，非目标打手不应误接该单。",
            "3. 丸友集订单列表、打手我的订单、丸掌柜订单详情中的来源单号、订单标题、打手信息一致。",
        ]
    ),
    "可组合/被引用关系": "FLOW-021/FLOW-022 的打手取消指派订单前置；必须接在 S-FLOW-014 后、S-FLOW-037 前。",
    "自动化状态": "已自动化",
    "备注": "2026-07-01新增：专门用于指派打手发单，不再复用 S-FLOW-021。",
}

S037_PRECONDITION = (
    "已执行 S-FLOW-079，丸友集已按指派打手方式发单给目标打手；指派单会直接进入被指派打手的我的订单，"
    "不需要再执行 S-FLOW-026 去抢单大厅接单。"
)
S037_UPDATES = {
    "所属模块": "指派单 下游取消订单",
    "用例标题": "打手代练中状态发起取消订单",
    "前置条件": S037_PRECONDITION,
    "执行步骤": "\n".join(
        [
            "1. 登录打手账号。",
            "2. 进入打手应用-我的订单。",
            "3. 打开指派单详情并记录订单状态为代练中。",
            "4. 点击取消订单。",
            "5. 填写取消原因并提交。",
            "6. 登录打手账号，进入打手应用-我的订单，回查订单状态。",
            "7. 登录丸友集账号，进入工作室履约应用-订单列表，回查取消申请。",
            "8. 登录丸掌柜账号，进入订单详情记录主单和子单状态。",
        ]
    ),
    "预期结果": "\n".join(
        [
            "1. 打手订单状态为取消中。",
            "2. 丸友集可见取消申请。",
            "3. 丸掌柜订单状态和子单状态不因打手单独取消而进入已取消终态。",
        ]
    ),
    "跨端/状态断言": "",
    "自动化状态": "已自动化",
    "备注": "2026-07-01修正：指派单发出后直接进入打手我的订单，S-FLOW-037 前置只需要 S-FLOW-079。",
}
S037_ASSERTION = "\n".join(
    [
        "1. 打手取消订单需要工作室审核。",
        "2. 打手取消订单不同于丸掌柜向下取消订单。",
        "3. S-FLOW-037 不能单独作为 FLOW-021 起点，必须承接 S-FLOW-079；指派单不需要再执行 S-FLOW-026。",
    ]
)
S037_UPDATES["跨端/状态断言"] = S037_ASSERTION

S038_UPDATES = {
    "所属模块": "指派单 下游取消订单",
    "用例标题": "打手取消中状态-丸友集拒绝取消",
    "前置条件": "打手已提交取消订单申请，丸友集待审核。",
    "执行步骤": "\n".join(
        [
            "1. 登录丸友集主账号或客服账号。",
            "2. 进入工作室履约应用-订单列表。",
            "3. 打开取消申请详情。",
            "4. 点击拒绝取消。",
            "5. 填写拒绝原因并提交。",
            "6. 登录丸友集账号，进入工作室履约应用-订单列表回查状态。",
            "7. 登录打手账号，进入打手应用-我的订单回查状态。",
            "8. 登录丸掌柜账号，进入订单详情记录主单和子单状态。",
        ]
    ),
    "预期结果": "\n".join(
        [
            "1. 打手订单状态恢复为代练中。",
            "2. 丸友集目标子单恢复为代练中。",
            "3. 丸掌柜订单状态为服务中。",
            "4. 丸掌柜子单状态为代练中。",
            "5. 打手侧可见拒绝原因。",
        ]
    ),
    "跨端/状态断言": "\n".join(
        [
            "1. 拒绝取消后不能残留取消中作为当前状态。",
            "2. 拒绝原因在丸友集和打手侧一致。",
        ]
    ),
    "自动化状态": "已自动化",
    "备注": "2026-07-01修正：该编号用于指派单下游取消订单拒绝分支。",
}

S039_UPDATES = {
    "所属模块": "指派单 下游取消订单",
    "用例标题": "打手取消中状态-丸友集同意取消",
    "前置条件": "打手已提交取消订单申请，丸友集待审核。",
    "执行步骤": "\n".join(
        [
            "1. 登录丸友集主账号或客服账号。",
            "2. 进入工作室履约应用-订单列表。",
            "3. 打开取消申请详情。",
            "4. 点击同意取消。",
            "5. 填写处理备注并提交。",
            "6. 登录丸友集账号，进入工作室履约应用-订单列表回查状态。",
            "7. 登录打手账号，进入打手应用-我的订单回查状态。",
            "8. 登录丸掌柜账号，进入订单详情记录主单和子单状态。",
        ]
    ),
    "预期结果": "\n".join(
        [
            "1. 打手订单状态为已取消。",
            "2. 丸友集目标子单状态为已取消。",
            "3. 丸友集父单根据是否需要继续履约展示为待继续发单、异常或页面对应状态。",
            "4. 丸掌柜主单不因单个打手子单取消直接已完成。",
        ]
    ),
    "跨端/状态断言": "\n".join(
        [
            "1. 取消审批记录、取消原因、子单终态可追踪。",
            "2. 如需继续履约，应进入二发或续建子单流程。",
        ]
    ),
    "自动化状态": "已自动化",
    "备注": "2026-07-01修正：该编号用于指派单下游取消订单同意分支。",
}


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


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


def set_row_values(ws, row: int, values: dict[str, str]) -> None:
    hs = headers(ws)
    for header, value in values.items():
        col = hs.get(header)
        if not col:
            continue
        cell = ws.cell(row, col)
        cell.value = value
        cell.alignment = copy(cell.alignment)
        cell.alignment = cell.alignment.copy(wrap_text=True, vertical="top")


def append_case_row(ws, values: dict[str, str], template_row: int | None = None) -> int:
    hs = headers(ws)
    row = ws.max_row + 1
    if template_row:
        for col in range(1, ws.max_column + 1):
            source = ws.cell(template_row, col)
            target = ws.cell(row, col)
            if source.has_style:
                target._style = copy(source._style)
            target.number_format = source.number_format
            target.protection = copy(source.protection)
            target.alignment = copy(source.alignment)
    for header, col in hs.items():
        ws.cell(row, col).value = values.get(header, "")
    return row


def sync_json() -> None:
    cases = json.loads(CASES_JSON.read_text(encoding="utf-8"))
    by_id = {item.get("用例编号"): item for item in cases}
    if "S-FLOW-021" in by_id:
        by_id["S-FLOW-021"].update(S021_UPDATES)
    if "S-FLOW-037" in by_id:
        by_id["S-FLOW-037"].update(S037_UPDATES)
    if "S-FLOW-038" in by_id:
        by_id["S-FLOW-038"].update(S038_UPDATES)
    if "S-FLOW-039" in by_id:
        by_id["S-FLOW-039"].update(S039_UPDATES)
    if "S-FLOW-079" in by_id:
        by_id["S-FLOW-079"].update(S079_ROW)
    else:
        cases.append(dict(S079_ROW))
    CASES_JSON.write_text(json.dumps(cases, ensure_ascii=False, indent=2), encoding="utf-8")


def sync_case_workbook() -> None:
    wb = load_workbook(CASE_WORKBOOK)
    ws = wb["订单状态流转"]
    row_021 = find_row(ws, "用例编号", "S-FLOW-021")
    if not row_021:
        raise RuntimeError("S-FLOW-021 not found")
    set_row_values(ws, row_021, S021_UPDATES)

    row_037 = find_row(ws, "用例编号", "S-FLOW-037")
    if row_037:
        set_row_values(ws, row_037, S037_UPDATES)
    row_038 = find_row(ws, "用例编号", "S-FLOW-038")
    if row_038:
        set_row_values(ws, row_038, S038_UPDATES)
    row_039 = find_row(ws, "用例编号", "S-FLOW-039")
    if row_039:
        set_row_values(ws, row_039, S039_UPDATES)

    row_079 = find_row(ws, "用例编号", "S-FLOW-079")
    if row_079:
        set_row_values(ws, row_079, S079_ROW)
    else:
        row_079 = append_case_row(ws, S079_ROW, template_row=row_021)
    wb.save(CASE_WORKBOOK)
    print(f"updated case workbook: S-FLOW-021 row={row_021}, S-FLOW-079 row={row_079}")


def sync_flow_workbook() -> None:
    wb = load_workbook(FLOW_WORKBOOK)
    flow_ws = wb["链路流程线"]
    detail_ws = wb["链路步骤明细"]
    flow_updates = {
        "FLOW-021": {
            "用例编号序列": "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-079 -> S-FLOW-037 -> S-FLOW-038",
            "用例链路（编号+标题）": (
                "S-FLOW-007(创建主单已推单) -> S-FLOW-014(丸友集确认接单) -> "
                "S-FLOW-079(丸友集指派打手发单；无打手则先邀请打手) -> "
                "S-FLOW-037(打手代练中状态发起取消订单) -> "
                "S-FLOW-038(打手取消中状态-丸友集拒绝取消)"
            ),
            "注意事项/缺口": "S-FLOW-037 前必须先执行 S-FLOW-079 指派打手发单；指派单直接进入打手我的订单，不接 S-FLOW-026。",
        },
        "FLOW-022": {
            "用例编号序列": "S-FLOW-007 -> S-FLOW-014 -> S-FLOW-079 -> S-FLOW-037 -> S-FLOW-039 -> S-FLOW-032",
            "用例链路（编号+标题）": (
                "S-FLOW-007(创建主单已推单) -> S-FLOW-014(丸友集确认接单) -> "
                "S-FLOW-079(丸友集指派打手发单；无打手则先邀请打手) -> "
                "S-FLOW-037(打手代练中状态发起取消订单) -> "
                "S-FLOW-039(打手取消中状态-丸友集同意取消) -> "
                "S-FLOW-032(前一子单已撤销/已取消状态-续发下一子单)"
            ),
            "注意事项/缺口": "S-FLOW-037 前必须先执行 S-FLOW-079 指派打手发单；指派单直接进入打手我的订单，同意取消后需校验旧子单与新子单互不覆盖。",
        },
    }
    hs = headers(flow_ws)
    for flow_id, values in flow_updates.items():
        row = find_row(flow_ws, "链路编号", flow_id)
        if not row:
            raise RuntimeError(f"{flow_id} not found")
        set_row_values(flow_ws, row, values)

    dh = headers(detail_ws)
    flow_col = dh["链路编号"]
    step_col = dh["步骤序号"]
    case_col = dh["用例编号"]
    module_col = dh.get("所属模块")
    title_col = dh.get("用例标题")
    pre_col = dh.get("前置条件")
    expected_col = dh.get("预期结果")

    def delete_flow_rows(flow_id: str) -> None:
        for row in range(detail_ws.max_row, 1, -1):
            if str(detail_ws.cell(row, flow_col).value or "").strip() == flow_id:
                detail_ws.delete_rows(row)

    for flow_id, sequence in {
        "FLOW-021": ["S-FLOW-007", "S-FLOW-014", "S-FLOW-079", "S-FLOW-037", "S-FLOW-038"],
        "FLOW-022": ["S-FLOW-007", "S-FLOW-014", "S-FLOW-079", "S-FLOW-037", "S-FLOW-039", "S-FLOW-032"],
    }.items():
        delete_flow_rows(flow_id)
        for index, case_id in enumerate(sequence, start=1):
            row = detail_ws.max_row + 1
            detail_ws.cell(row, flow_col).value = flow_id
            detail_ws.cell(row, step_col).value = index
            detail_ws.cell(row, case_col).value = case_id
            case = S079_ROW if case_id == "S-FLOW-079" else {}
            if module_col:
                detail_ws.cell(row, module_col).value = case.get("所属模块", "")
            if title_col:
                detail_ws.cell(row, title_col).value = case.get("用例标题", "")
            if pre_col:
                detail_ws.cell(row, pre_col).value = S037_PRECONDITION if case_id == "S-FLOW-037" else case.get("前置条件", "")
            if expected_col:
                detail_ws.cell(row, expected_col).value = case.get("预期结果", "")

    wb.save(FLOW_WORKBOOK)
    print("updated flow workbook FLOW-021/FLOW-022")


def main() -> None:
    sync_json()
    sync_case_workbook()
    sync_flow_workbook()


if __name__ == "__main__":
    main()
