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_WORKBOOKS = sorted((ROOT / "测试用例").glob("SaaS_E2E测试用例_单Sheet用例测试列表*.xlsx"))
FLOW_WORKBOOKS = sorted((ROOT / "测试用例").glob("订单状态流转_链路流程线整理*.xlsx"))
BACKUP_DIR = ROOT / "runtime" / "excel_backups"


CASE_045 = {
    "用例编号": "S-FLOW-045",
    "用例层级": "订单状态流转用例",
    "来源/组合依据": "FLOW-044：丸友集申请上游验收后取消验收",
    "所属模块": "订单验收",
    "用例标题": "【丸友集视角】打手子单已完成 / 丸掌柜主单服务中 - 申请上游验收",
    "登录平台/角色账号": "丸友集：工作室主账号或客服账号；丸掌柜：企业主账号或客服账号。",
    "前置条件": "已执行 S-FLOW-043，丸友集已同意打手子单验收，目标订单在丸友集订单列表展示可申请上游验收；丸掌柜主单仍处于服务中/代练中。",
    "测试数据": "来源单号、订单标题、丸掌柜主单号、丸友集父单号、打手子单号、申请验收说明。",
    "执行步骤": "\n".join(
        [
            "1. 登录丸友集工作室主账号或客服账号。",
            "2. 进入工作室履约应用-订单列表。",
            "3. 按来源单号查询目标订单。",
            "4. 确认目标订单存在已完成打手子单，且展示申请上游验收入口。",
            "5. 点击申请上游验收。",
            "6. 在弹框中填写验收说明/备注并提交。",
            "7. 回到丸友集订单列表，按来源单号回查目标订单。",
            "8. 登录丸掌柜账号，进入订单详情回查主单和子单状态。",
        ]
    ),
    "预期结果": "\n".join(
        [
            "1. 丸友集成功申请上游验收。",
            "2. 丸友集订单列表展示等待上游验收/待验收状态，并出现取消验收入口。",
            "3. 丸掌柜订单详情展示主单待验收或页面对应待上游验收状态。",
            "4. 台账状态流转为 upstream_acceptance_applied，后续可接 S-FLOW-082 或 S-FLOW-049。",
        ]
    ),
    "跨端/状态断言": "\n".join(
        [
            "1. 丸友集侧来源单号与丸掌柜详情来源单号一致。",
            "2. 申请上游验收后，丸友集侧可取消验收，丸掌柜侧可继续同意验收。",
            "3. 该动作不同于丸友集同意打手子单验收，不能省略 S-FLOW-043。",
        ]
    ),
    "可组合/被引用关系": "FLOW-026、FLOW-027 等上游验收链路引用；取消验收分支由 FLOW-044 覆盖；承接 S-FLOW-043，后续可接 S-FLOW-082 或 S-FLOW-049。",
    "自动化状态": "已自动化",
    "备注": "2026-07-02修正：该编号在自动化中表示丸友集申请上游验收，原申诉含义已废弃。",
}


CASE_082 = {
    "用例编号": "S-FLOW-082",
    "用例层级": "订单状态流转用例",
    "来源/组合依据": "实际平台补充：丸友集申请上游验收后可取消验收",
    "所属模块": "订单验收",
    "用例标题": "【丸友集视角】已申请上游验收 / 丸掌柜主单待验收 - 取消验收并双端回查状态",
    "登录平台/角色账号": "丸友集：工作室主账号或客服账号；丸掌柜：企业主账号或客服账号。",
    "前置条件": "已执行 S-FLOW-045，丸友集侧订单处于等待上游验收/待验收状态，订单列表操作区展示取消验收入口。",
    "测试数据": "来源单号、订单标题、丸掌柜主单号、丸友集父单号、取消验收原因。",
    "执行步骤": "\n".join(
        [
            "1. 登录丸友集工作室主账号或客服账号。",
            "2. 进入工作室履约应用-订单列表。",
            "3. 按来源单号查询目标订单。",
            "4. 确认订单处于等待上游验收/待验收状态，并展示取消验收按钮。",
            "5. 点击取消验收。",
            "6. 在取消验收弹框中填写取消原因。",
            "7. 点击确认取消。",
            "8. 回到丸友集订单列表，按来源单号回查该订单状态和操作入口。",
            "9. 登录丸掌柜账号，进入订单详情，按主单号/来源单号定位同一订单。",
            "10. 回查丸掌柜主单状态和子单状态。",
            "11. 再回到丸友集订单列表按来源单号复核状态未停留在待取消验收状态。",
        ]
    ),
    "预期结果": "\n".join(
        [
            "1. 丸友集取消验收提交成功。",
            "2. 丸友集订单列表不再停留在等待上游验收/待取消验收状态，可重新展示申请上游验收或页面对应履约完成状态。",
            "3. 丸掌柜订单详情从待验收回到服务中/代练中或页面对应可继续履约状态，子单状态保持已完成/服务中/代练中之一。",
            "4. 两端来源单号、订单标题、主单号匹配同一订单。",
            "5. 台账状态流转为 upstream_acceptance_cancelled，后续可重新接 S-FLOW-045。",
        ]
    ),
    "跨端/状态断言": "\n".join(
        [
            "1. 取消验收只撤回上游验收申请，不应取消已完成的打手子单。",
            "2. 丸友集侧取消后不应继续展示取消验收作为唯一后续操作。",
            "3. 丸掌柜侧不应仍停留纯待验收终态，应回到服务中/代练中或页面对应履约状态。",
        ]
    ),
    "可组合/被引用关系": "新增 FLOW-044 引用；承接 S-FLOW-045，取消后回到可重新申请 S-FLOW-045 的状态。",
    "自动化状态": "已自动化",
    "备注": "2026-07-02新增：补齐丸友集申请上游验收后取消验收，并同步回查丸友集侧和丸掌柜侧状态。",
}


FLOW_044_CASE_IDS = [
    "S-FLOW-007",
    "S-FLOW-014",
    "S-FLOW-021",
    "S-FLOW-026",
    "S-FLOW-040",
    "S-FLOW-043",
    "S-FLOW-045",
    "S-FLOW-082",
]


FLOW_044 = {
    "链路编号": "FLOW-044",
    "链路名称": "打手提交验收-丸友集同意验收-申请上游验收-取消验收",
    "链路类型": "上游验收取消分支",
    "起点状态": "丸友集打手子单已完成，丸掌柜主单可申请上游验收",
    "终点状态": "上游验收申请已撤回，订单回到可重新申请上游验收/继续履约状态",
    "用例编号序列": " -> ".join(FLOW_044_CASE_IDS),
    "适用场景": "验证丸友集申请上游验收后可撤回，并核对丸友集侧与丸掌柜侧状态同步回退。",
    "数据复用建议": "复用已走到 S-FLOW-043 的订单；执行 S-FLOW-045 后立即执行 S-FLOW-082，不与 S-FLOW-049 同意验收共用同一次申请。",
    "注意事项/缺口": "该链路编号按当前最大编号追加为 FLOW-044，但业务排序放在 FLOW-026 前；取消验收后只开放重新申请 S-FLOW-045，不应直接接丸掌柜同意验收。",
}


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:
    if sheet.row_dimensions[source_row].height is not None:
        sheet.row_dimensions[target_row].height = sheet.row_dimensions[source_row].height
    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)
        if source.font:
            target.font = copy(source.font)
        if source.fill:
            target.fill = copy(source.fill)
        if source.border:
            target.border = copy(source.border)
        if source.protection:
            target.protection = copy(source.protection)


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}
    if "S-FLOW-045" in by_id:
        by_id["S-FLOW-045"].update(CASE_045)
    else:
        cases.append(dict(CASE_045))
        by_id["S-FLOW-045"] = cases[-1]
    if "S-FLOW-082" in by_id:
        by_id["S-FLOW-082"].update(CASE_082)
    else:
        insert_at = cases.index(by_id["S-FLOW-045"]) + 1
        cases.insert(insert_at, dict(CASE_082))
    CASE_JSON.write_text(json.dumps(cases, ensure_ascii=False, indent=2), encoding="utf-8")


def upsert_case_rows(workbook_path: Path) -> None:
    backup_workbook(workbook_path)
    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-045") or row_by_case.get("S-FLOW-043") or 2
            for item in [CASE_045, CASE_082]:
                case_id = item["用例编号"]
                row = row_by_case.get(case_id)
                if row is None:
                    row = row_by_case.get("S-FLOW-045", sheet.max_row) + 1 if case_id == "S-FLOW-082" else sheet.max_row + 1
                    sheet.insert_rows(row)
                    copy_row_style(sheet, style_row, row)
                    row_by_case = {
                        str(sheet.cell(row_index, case_col).value or "").strip(): row_index
                        for row_index in range(2, sheet.max_row + 1)
                    }
                    row_by_case[case_id] = row
                for header, value in item.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(case_workbook_path: Path) -> dict[str, dict[str, str]]:
    workbook = openpyxl.load_workbook(case_workbook_path, 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 case_id:
                result[case_id] = {
                    header: str(sheet.cell(row, column).value or "")
                    for header, column in headers.items()
                }
        return result
    finally:
        workbook.close()


def flow_refs(case_map: dict[str, dict[str, str]]) -> str:
    return " -> ".join(f"{case_id}({case_map.get(case_id, {}).get('用例标题', '')})" for case_id in FLOW_044_CASE_IDS)


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


def upsert_flow_workbook(workbook_path: Path, case_workbook_path: Path) -> None:
    backup_workbook(workbook_path)
    case_map = load_case_map(case_workbook_path)
    workbook = openpyxl.load_workbook(workbook_path)
    try:
        summary = workbook["链路流程线"]
        headers = headers_for(summary)
        flow_col = headers["链路编号"]
        row_by_flow = {
            str(summary.cell(row, flow_col).value or "").strip(): row
            for row in range(2, summary.max_row + 1)
        }
        for old_row in sorted(
            [row for flow_id, row in row_by_flow.items() if flow_id in {"FLOW-025A", "FLOW-044"}],
            reverse=True,
        ):
            summary.delete_rows(old_row, 1)
        row_by_flow = {
            str(summary.cell(row, flow_col).value or "").strip(): row
            for row in range(2, summary.max_row + 1)
        }
        flow_row = row_by_flow.get("FLOW-026", summary.max_row + 1)
        summary.insert_rows(flow_row, 1)
        style_source_row = flow_row + 1 if flow_row <= summary.max_row else row_by_flow.get("FLOW-025", 2)
        copy_row_style(summary, style_source_row, flow_row)
        for header, value in FLOW_044.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 = flow_refs(case_map)
        module_col = headers.get("涉及模块")
        if module_col:
            summary.cell(flow_row, module_col).value = flow_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() in {"FLOW-025A", "FLOW-044"}:
                detail.delete_rows(row, 1)
        detail_rows_by_flow: dict[str, list[int]] = {}
        for row in range(2, detail.max_row + 1):
            detail_rows_by_flow.setdefault(str(detail.cell(row, detail_flow_col).value or "").strip(), []).append(row)
        insert_at = (detail_rows_by_flow.get("FLOW-026") or [detail.max_row + 1])[0]
        style_row = (detail_rows_by_flow.get("FLOW-026") or detail_rows_by_flow.get("FLOW-025") or [2])[0]
        for index, case_id in enumerate(FLOW_044_CASE_IDS, start=1):
            row = insert_at + index - 1
            detail.insert_rows(row, 1)
            copy_row_style(detail, style_row + index if style_row >= insert_at else style_row, row)
            case = case_map.get(case_id, {})
            values = {
                "链路编号": "FLOW-044",
                "步骤序号": index,
                "用例编号": case_id,
                "所属模块": case.get("所属模块", ""),
                "用例标题": case.get("用例标题", ""),
                "前置条件": case.get("前置条件", ""),
                "预期结果": case.get("预期结果", ""),
            }
            for header, value in values.items():
                column = detail_headers.get(header)
                if column:
                    detail.cell(row, column).value = value

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


def main() -> None:
    if not CASE_WORKBOOKS or not FLOW_WORKBOOKS:
        raise RuntimeError("未找到测试用例 Excel 或链路 Excel")
    update_case_json()
    for workbook_path in CASE_WORKBOOKS:
        upsert_case_rows(workbook_path)
    primary_case_workbook = ROOT / "测试用例" / "SaaS_E2E测试用例_单Sheet用例测试列表.xlsx"
    case_workbook_path = primary_case_workbook if primary_case_workbook.exists() else CASE_WORKBOOKS[0]
    for workbook_path in FLOW_WORKBOOKS:
        upsert_flow_workbook(workbook_path, case_workbook_path)
    print("Synced S-FLOW-045/S-FLOW-082 and placed FLOW-044 before FLOW-026")


if __name__ == "__main__":
    main()
