from __future__ import annotations

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

import openpyxl


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


CASE_UPDATES = {
    "S-FLOW-043": {
        "可组合/被引用关系": "FLOW-025、FLOW-026 等验收后续链路引用；FLOW-025 中后续先接 S-FLOW-080/S-FLOW-081 完成打手结算，再接 S-FLOW-044 续发下一子单。",
        "备注": "2026-07-02修正：该编号在 FLOW-025 链路中表示丸友集同意验收打手子单；同意验收后生成打手收入，但不直接接续发下一单。",
    },
    "S-FLOW-044": {
        "前置条件": "已执行 S-FLOW-043 丸友集同意验收前一打手子单，且已执行 S-FLOW-080/S-FLOW-081 完成打手端发起结算和丸友集端完成结算；前一子单处于已完成/已结算，主单仍需继续履约且页面允许继续发单。",
        "可组合/被引用关系": "FLOW-025 引用；必须接在 S-FLOW-081 后，后续接 S-FLOW-026。",
        "备注": "2026-07-02修正：FLOW-025 中 S-FLOW-044 不再直接承接 S-FLOW-043，需先完成打手结算。",
    },
    "S-FLOW-080": {
        "用例编号": "S-FLOW-080",
        "用例层级": "订单状态流转用例",
        "来源/组合依据": "FLOW-025：打手子单验收后结算闭环",
        "所属模块": "结算",
        "用例标题": "【打手视角】子单已验收 / 我的收入已生成 - 发起结算并提交账单",
        "登录平台/角色账号": "打手：已绑定丸友集工作室的打手子账号。",
        "前置条件": "已执行 S-FLOW-043，丸友集已同意验收打手子单；打手我的收入中存在该来源单号的可结算收入；打手打款账户已完善或可在用例中补充完善。",
        "测试数据": "来源单号、订单标题、丸掌柜主单号、丸友集父单号、打手子单号、预期打手收入金额、打手打款账户信息。",
        "执行步骤": "\n".join(
            [
                "1. 登录打手账号。",
                "2. 进入打手应用-我的收入。",
                "3. 按来源单号查询已验收生成收入的目标订单。",
                "4. 如打款账户未完善，补充收款人、手机号、收款账户、开户行/银行和身份证等必填信息并保存。",
                "5. 勾选目标收入订单。",
                "6. 点击一键结算。",
                "7. 在结算确认页核对订单、金额和打款账户信息。",
                "8. 点击提交账单或确认提交。",
                "9. 回到我的收入或结算页面确认账单已提交/待审核/待打款。",
            ]
        ),
        "预期结果": "\n".join(
            [
                "1. 打手可对该已验收收入发起结算。",
                "2. 结算账单提交成功，页面出现已提交、待审核、待打款、账单号或结算中等状态。",
                "3. 目标订单金额与 S-FLOW-043 生成的打手收入一致。",
                "4. 台账状态流转为 player_settlement_bill_submitted，后续只允许接 S-FLOW-081。",
            ]
        ),
        "跨端/状态断言": "\n".join(
            [
                "1. 同意验收后必须先发起结算，不能直接续发下一子单。",
                "2. 打手结算账单的来源单号、订单标题和金额与已验收子单一致。",
                "3. 丸友集端应可在财务结算/结算账单中处理该账单。",
            ]
        ),
        "可组合/被引用关系": "FLOW-025 引用；承接 S-FLOW-043，后续接 S-FLOW-081。",
        "自动化状态": "已自动化",
        "备注": "2026-07-02新增：补齐 FLOW-025 中丸友集同意验收后、续发下一子单前的打手发起结算步骤。",
    },
    "S-FLOW-081": {
        "用例编号": "S-FLOW-081",
        "用例层级": "订单状态流转用例",
        "来源/组合依据": "FLOW-025：打手子单验收后结算闭环",
        "所属模块": "结算",
        "用例标题": "【丸友集视角】打手结算账单待处理 - 完成结算并使子单转为已完成",
        "登录平台/角色账号": "丸友集：工作室主账号或具备财务结算权限的客服账号；打手：用于回查我的收入已结算状态。",
        "前置条件": "已执行 S-FLOW-080，打手已提交该来源单号的结算账单；丸友集具备处理打手结算账单的权限；如选择线上打款，丸友集线上打款配置已可用。",
        "测试数据": "来源单号、订单标题、丸掌柜主单号、丸友集父单号、打手子单号、结算方式、预期结算金额。",
        "执行步骤": "\n".join(
            [
                "1. 登录丸友集主账号或具备结算权限的账号。",
                "2. 进入工作室基础应用-财务结算列表-结算账单列表。",
                "3. 按来源单号或订单标题查询打手提交的结算账单。",
                "4. 打开账单并核对打手、订单、金额和结算方式。",
                "5. 选择线下支付或线上打款并完成结算。",
                "6. 回到丸友集订单列表，按来源单号查询目标订单。",
                "7. 确认前一打手子单由已验收/收入已生成转为已完成/已结算。",
                "8. 登录打手账号进入我的收入，按来源单号回查该收入为已完成且已结算。",
            ]
        ),
        "预期结果": "\n".join(
            [
                "1. 丸友集成功完成打手结算账单。",
                "2. 打手我的收入中该订单展示已完成且已结算/已打款。",
                "3. 丸友集订单列表或子单列表中前一子单展示已完成/已结算。",
                "4. 主单仍不因单个子单完成直接完结，仍可继续发下一子单。",
                "5. 台账状态流转为 player_sub_order_settled_completed_ready_for_next_dispatch，后续可接 S-FLOW-044。",
            ]
        ),
        "跨端/状态断言": "\n".join(
            [
                "1. 结算金额、来源单号、订单标题在丸友集账单、丸友集订单列表和打手我的收入中一致。",
                "2. S-FLOW-044 必须基于已完成/已结算子单续发，不能基于仅已验收的子单续发。",
                "3. 完成的是打手子单结算，不等于丸掌柜主单验收完成。",
            ]
        ),
        "可组合/被引用关系": "FLOW-025 引用；承接 S-FLOW-080，后续接 S-FLOW-044。",
        "自动化状态": "已自动化",
        "备注": "2026-07-02新增：补齐 FLOW-025 中丸友集完成打手结算，使前一子单从已验收转为已完成后再续发下一单。",
    },
}


FLOW_025_CASE_IDS = [
    "S-FLOW-007",
    "S-FLOW-014",
    "S-FLOW-021",
    "S-FLOW-026",
    "S-FLOW-040",
    "S-FLOW-043",
    "S-FLOW-080",
    "S-FLOW-081",
    "S-FLOW-044",
]


FLOW_025_UPDATES = {
    "链路名称": "打手提交验收-丸友集同意验收-打手发起结算-丸友集完成结算-续发下一子单",
    "链路类型": "子单验收结算后多子单续发",
    "起点状态": "丸友集打手子单待验收",
    "终点状态": "前一子单已验收并已完成结算；下一子单待接单/代练中",
    "用例编号序列": " -> ".join(FLOW_025_CASE_IDS),
    "适用场景": "验证丸友集同意验收后，需先由打手发起结算、丸友集完成结算，使前一子单从已验收转为已完成，再继续发下一子单。",
    "数据复用建议": "可复用同一主单：完成首个打手子单验收与结算后，再通过 S-FLOW-044 续发下一子单。",
    "注意事项/缺口": "S-FLOW-043 后不直接接 S-FLOW-044；先接 S-FLOW-080/S-FLOW-081 完成打手结算，主单不应因单个子单完成直接完结。",
}


def headers_for(sheet) -> dict[str, int]:
    return {
        str(sheet.cell(1, column).value or "").strip(): column
        for column in range(1, sheet.max_column + 1)
        if str(sheet.cell(1, column).value or "").strip()
    }


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


def copy_row_style(sheet, source_row: int, target_row: int) -> None:
    for column in range(1, sheet.max_column + 1):
        source = sheet.cell(source_row, column)
        target = sheet.cell(target_row, column)
        if source.has_style:
            target._style = copy(source._style)
        if source.number_format:
            target.number_format = source.number_format
        if source.alignment:
            target.alignment = copy(source.alignment)


def update_case_json() -> None:
    cases = json.loads(CASE_JSON.read_text(encoding="utf-8"))
    by_id = {str(item.get("用例编号", "")).strip(): item for item in cases}
    insert_after = "S-FLOW-044"
    for case_id, updates in CASE_UPDATES.items():
        if case_id in by_id:
            by_id[case_id].update(updates)
            continue
        item = dict(updates)
        if insert_after in by_id:
            index = cases.index(by_id[insert_after]) + 1
            cases.insert(index, item)
        else:
            cases.append(item)
        by_id[case_id] = item
        insert_after = case_id
    CASE_JSON.write_text(json.dumps(cases, ensure_ascii=False, indent=2), encoding="utf-8")


def upsert_case_rows(workbook_path: Path) -> None:
    workbook = openpyxl.load_workbook(workbook_path)
    try:
        for sheet in workbook.worksheets:
            headers = headers_for(sheet)
            case_col = headers.get("用例编号")
            if not case_col:
                continue
            row_by_case = {
                str(sheet.cell(row, case_col).value or "").strip(): row
                for row in range(2, sheet.max_row + 1)
            }
            style_row = row_by_case.get("S-FLOW-043") or 2
            for case_id, updates in CASE_UPDATES.items():
                row = row_by_case.get(case_id)
                if not row:
                    row = sheet.max_row + 1
                    copy_row_style(sheet, style_row, row)
                    sheet.cell(row, case_col).value = case_id
                    row_by_case[case_id] = row
                for header, value in updates.items():
                    column = headers.get(header)
                    if not column:
                        continue
                    cell = sheet.cell(row, column)
                    cell.value = value
                    alignment = copy(cell.alignment)
                    alignment.wrap_text = True
                    alignment.vertical = "top"
                    cell.alignment = alignment
        workbook.save(workbook_path)
    finally:
        workbook.close()


def load_case_map() -> dict[str, dict[str, str]]:
    workbook = openpyxl.load_workbook(CASE_WORKBOOK, data_only=True)
    try:
        sheet = workbook["订单状态流转"]
        headers = headers_for(sheet)
        case_col = headers["用例编号"]
        result: dict[str, dict[str, str]] = {}
        for row in range(2, sheet.max_row + 1):
            case_id = str(sheet.cell(row, case_col).value or "").strip()
            if not case_id:
                continue
            result[case_id] = {
                header: str(sheet.cell(row, column).value or "")
                for header, column in headers.items()
            }
        return result
    finally:
        workbook.close()


def joined_modules(case_map: dict[str, dict[str, str]]) -> str:
    modules: list[str] = []
    for case_id in FLOW_025_CASE_IDS:
        module = case_map.get(case_id, {}).get("所属模块", "")
        if module and module not in modules:
            modules.append(module)
    return " -> ".join(modules)


def update_flow_workbook() -> None:
    case_map = load_case_map()
    workbook = openpyxl.load_workbook(FLOW_WORKBOOK)
    try:
        summary = workbook["链路流程线"]
        headers = headers_for(summary)
        flow_col = headers["链路编号"]
        flow_row = None
        for row in range(2, summary.max_row + 1):
            if str(summary.cell(row, flow_col).value or "").strip() == "FLOW-025":
                flow_row = row
                break
        if flow_row is None:
            flow_row = summary.max_row + 1
            copy_row_style(summary, 2, flow_row)
            summary.cell(flow_row, flow_col).value = "FLOW-025"
        for header, value in FLOW_025_UPDATES.items():
            column = headers.get(header)
            if column:
                summary.cell(flow_row, column).value = value
        ref_col = headers.get("用例链路（编号+标题）")
        if ref_col:
            summary.cell(flow_row, ref_col).value = " -> ".join(
                f"{case_id}({case_map.get(case_id, {}).get('用例标题', '')})"
                for case_id in FLOW_025_CASE_IDS
            )
        module_col = headers.get("涉及模块")
        if module_col:
            summary.cell(flow_row, module_col).value = joined_modules(case_map)

        detail = workbook["链路步骤明细"]
        detail_headers = headers_for(detail)
        detail_flow_col = detail_headers["链路编号"]
        for row in range(detail.max_row, 1, -1):
            if str(detail.cell(row, detail_flow_col).value or "").strip() == "FLOW-025":
                detail.delete_rows(row, 1)
        style_row = 2
        for row in range(2, detail.max_row + 1):
            if str(detail.cell(row, detail_flow_col).value or "").strip():
                style_row = row
                break
        for index, case_id in enumerate(FLOW_025_CASE_IDS, start=1):
            row = detail.max_row + 1
            copy_row_style(detail, style_row, row)
            values = {
                "链路编号": "FLOW-025",
                "步骤序号": index,
                "用例编号": case_id,
                "所属模块": case_map.get(case_id, {}).get("所属模块", ""),
                "用例标题": case_map.get(case_id, {}).get("用例标题", ""),
                "前置条件": case_map.get(case_id, {}).get("前置条件", ""),
                "预期结果": case_map.get(case_id, {}).get("预期结果", ""),
            }
            for header, value in values.items():
                column = detail_headers.get(header)
                if column:
                    detail.cell(row, column).value = value

        workbook.save(FLOW_WORKBOOK)
    finally:
        workbook.close()


def main() -> None:
    backup_workbook(CASE_WORKBOOK)
    backup_workbook(FLOW_WORKBOOK)
    update_case_json()
    upsert_case_rows(CASE_WORKBOOK)
    upsert_case_rows(FLOW_WORKBOOK)
    update_flow_workbook()
    print("Synced FLOW-025 settlement chain: S-FLOW-043 -> S-FLOW-080 -> S-FLOW-081 -> S-FLOW-044")


if __name__ == "__main__":
    main()
