#!/usr/bin/env python3
"""Create/update the first regulator dual-database Apifox scenarios."""

from __future__ import annotations

import copy
import importlib.util
import json
from datetime import datetime
from pathlib import Path
from typing import Any


HERE = Path(__file__).resolve().parent
SHARED_PATH = HERE / "sync_school_s00_s06_scenarios.py"
SPEC = importlib.util.spec_from_file_location("school_sync", SHARED_PATH)
if SPEC is None or SPEC.loader is None:
    raise RuntimeError(f"cannot load shared Apifox helper: {SHARED_PATH}")
SHARED = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(SHARED)

Apifox = SHARED.Apifox
PROJECT_ID = SHARED.PROJECT_ID
MYSQL_BASE_URL = SHARED.MYSQL_BASE_URL
DM_BASE_URL = SHARED.DM_BASE_URL
make_login = SHARED.make_login
make_request = SHARED.make_request
new_id = SHARED.new_id
flatten_requests = SHARED.flatten_requests
request_has_auditable_evidence = SHARED.request_has_auditable_evidence

FOLDER_NAME = "监管端-双库兼容"
SUITE_NAME = "监管端-双库全量一键回归"
BACKUP_DIR = HERE.parent / "backups"


def test(name: str, statement: str) -> tuple[str, str]:
    return name, statement


SCENARIOS: dict[str, dict[str, Any]] = {
    "R01-监管首页与学校管理（双库真实数据）": {
        "prefix": "APIFOX_R01",
        "description": (
            "监管端首页与学校基础数据双库场景。当前固化已在 MySQL foodsafe_test "
            "和达梦 WWWWEB 实际返回 200 的读取链路；两库业务数量可不同，"
            "但响应结构、类型和权限要求必须一致。"
        ),
        "operations": [
            {
                "name": "监管首页总览",
                "method": "GET",
                "path": "/reg/overview",
                "tests": [
                    test("首页返回七组聚合数据", "pm.expect(__json.data).to.include.all.keys('kpis','dineToday','ledgerToday','aiByType','dailyReports','topWarnings','expiringCerts')"),
                    test("首页 KPI 为对象", "pm.expect(__json.data.kpis).to.be.an('object')"),
                ],
            },
            {
                "name": "监管学校统计列表",
                "method": "GET",
                "path": "/reg/schools/stats",
                "tests": [
                    test("学校统计为非空数组", "pm.expect(__json.data).to.be.an('array').that.is.not.empty"),
                    test("学校统计包含学校识别字段", "pm.expect(__json.data.some(function(x){ return x.id !== undefined || x.schoolId !== undefined; })).to.equal(true)"),
                ],
            },
            {
                "name": "学校配置列表",
                "method": "GET",
                "path": "/reg/schools/config",
                "tests": [
                    test("学校配置为非空数组", "pm.expect(__json.data).to.be.an('array').that.is.not.empty"),
                    test("学校名称为非空文本", "pm.expect(__json.data.some(function(x){ return String(x.name || '').trim().length > 0; })).to.equal(true)"),
                ],
            },
            {
                "name": "证照总览",
                "method": "GET",
                "path": "/reg/credentials/overview",
                "tests": [
                    test("证照总览包含统计和临期列表", "pm.expect(__json.data).to.include.all.keys('stats','expiringSoonList'); pm.expect(__json.data.expiringSoonList).to.be.an('array')"),
                ],
            },
        ],
    },
    "R02-监管预警与晨检台账（双库真实数据）": {
        "prefix": "APIFOX_R02",
        "description": (
            "监管预警和晨检台账双库读取基线。验证监管专用路由、分页、"
            "真实记录和基本数据结构；受限账号默认范围和外校 403 将在同场景后续增补。"
        ),
        "operations": [
            {
                "name": "监管预警列表",
                "method": "GET",
                "path": "/reg/alerts/events?page=1&pageSize=20",
                "tests": [
                    test("预警分页结构完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(Number(__json.data.total)).to.be.at.least(__json.data.items.length); pm.expect(Number(__json.data.page)).to.equal(1)"),
                    test("预警列表返回真实数据", "pm.expect(__json.data.items.length).to.be.greaterThan(0)"),
                ],
            },
            {
                "name": "监管预警类型汇总",
                "method": "GET",
                "path": "/reg/alerts/summary",
                "tests": [
                    test("预警汇总 data 存在", "pm.expect(__json).to.have.property('data')"),
                ],
            },
            {
                "name": "监管晨检台账",
                "method": "GET",
                "path": "/reg/ledgers/morning?page=1&pageSize=20",
                "tests": [
                    test("晨检台账分页结构完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(Number(__json.data.total)).to.be.at.least(__json.data.items.length)"),
                    test("晨检台账返回真实记录", "pm.expect(__json.data.items.length).to.be.greaterThan(0)"),
                ],
            },
        ],
    },
    "R03-检查任务（双库真实写入）": {
        "prefix": "APIFOX_R03",
        "description": (
            "监管检查任务双库真实写入场景。用中文业务数据完成创建、详情读回、"
            "开始执行、提交不合格检查结果和提交后再读回。记录保留于两个测试库，"
            "供 regulator_inspections_tasks 原表核对。"
        ),
        "operations": [
            {
                "name": "创建秋季开学食品安全专项检查",
                "method": "POST",
                "path": "/reg/inspections/tasks",
                "body": {
                    "type": "日常检查",
                    "schoolId": "6",
                    "assignee": "王伟",
                    "grid": "第一监管网格",
                    "content": "{{test_batch}}_秋季开学食品安全专项检查",
                    "planAt": "2026-09-28T09:30:00+08:00",
                },
                "extract": ("reg_task_id", "$.data.id"),
                "tests": [
                    test("创建返回 CHK 任务编号", "pm.expect(String(__json.data.id)).to.match(/^CHK-/)"),
                    test("创建任务属于学校 6", "pm.expect(String(__json.data.schoolId)).to.equal('6')"),
                    test("创建任务为待处理", "pm.expect(__json.data.status).to.equal('待处理')"),
                ],
            },
            {
                "name": "读回新创建的检查任务",
                "method": "GET",
                "path": "/reg/inspections/tasks/{{reg_task_id}}",
                "tests": [
                    test("详情 ID 与创建 ID 一致", "pm.expect(String(__json.data.id)).to.equal(String(pm.variables.get('reg_task_id')))"),
                    test("详情保留中文任务名", "pm.expect(__json.data.content).to.equal(pm.variables.get('test_batch') + '_秋季开学食品安全专项检查')"),
                ],
            },
            {
                "name": "开始现场检查",
                "method": "PATCH",
                "path": "/reg/inspections/tasks/{{reg_task_id}}",
                "body": {"status": "进行中", "content": "{{test_batch}}_秋季开学食品安全专项检查（现场检查）"},
                "tests": [
                    test("任务状态已更新为进行中", "pm.expect(__json.data.status).to.equal('进行中')"),
                    test("现场检查中文内容未乱码", "pm.expect(__json.data.content).to.include('现场检查')"),
                ],
            },
            {
                "name": "提交不合格检查结果",
                "method": "POST",
                "path": "/reg/inspections/tasks/{{reg_task_id}}/submit",
                "body": {
                    "passed": False,
                    "items": [{"item": "食品留样记录完整性", "ok": False, "remark": "留样标签需补充留样时间"}],
                    "summary": "现场检查发现留样标签信息不完整，已要求当日整改。",
                    "images": [],
                },
                "expected_http": 201,
                "tests": [
                    test("提交后任务为已完成", "pm.expect(__json.data.status).to.equal('已完成')"),
                ],
            },
            {
                "name": "提交后再读回检查结果",
                "method": "GET",
                "path": "/reg/inspections/tasks/{{reg_task_id}}",
                "tests": [
                    test("任务状态持久为已完成", "pm.expect(__json.data.status).to.equal('已完成')"),
                    test("检查结论为真实中文内容", "pm.expect(JSON.stringify(__json.data)).to.include('留样标签信息不完整')"),
                ],
            },
        ],
    },
    "R04-资质证照管理（读取通过、处置待修）": {
        "prefix": "APIFOX_R04",
        "description": (
            "监管资质证照双库真实读取场景。食堂、人员、供应商、异常、总览和 CSV "
            "均读取当前测试库真实数据；异常处置因 DBCOMPAT-0024 尚未持久化而默认关闭，"
            "修复前不将进程内假成功计为通过。"
        ),
        "operations": [
            {
                "name": "食堂证照列表",
                "method": "GET",
                "path": "/reg/credentials/canteens",
                "tests": [test("食堂证照返回真实数组", "pm.expect(__json.data).to.be.an('array').that.is.not.empty")],
            },
            {
                "name": "人员健康证列表",
                "method": "GET",
                "path": "/reg/credentials/workers",
                "tests": [test("人员健康证返回真实数组", "pm.expect(__json.data).to.be.an('array').that.is.not.empty")],
            },
            {
                "name": "供应商证照列表",
                "method": "GET",
                "path": "/reg/credentials/suppliers",
                "tests": [test("供应商证照返回真实数组", "pm.expect(__json.data).to.be.an('array').that.is.not.empty")],
            },
            {
                "name": "证照异常列表",
                "method": "GET",
                "path": "/reg/credentials/exceptions",
                "extract": ("credential_exception_id", "$.data[0].id"),
                "tests": [
                    test("证照异常返回真实记录", "pm.expect(__json.data).to.be.an('array').that.is.not.empty"),
                    test("异常主体和证件类型存在", "pm.expect(__json.data[0].entityName).to.be.a('string').and.not.empty; pm.expect(__json.data[0].certificateType).to.be.a('string').and.not.empty"),
                ],
            },
            {
                "name": "证照风险总览",
                "method": "GET",
                "path": "/reg/credentials/overview",
                "tests": [test("证照总览结构完整", "pm.expect(__json.data).to.include.all.keys('stats','expiringSoonList'); pm.expect(__json.data.expiringSoonList).to.be.an('array')")],
            },
            {
                "name": "导出证照异常 CSV",
                "method": "GET",
                "path": "/reg/credentials/export.csv?target=exceptions",
                "tests": [
                    test("CSV 正文为非空文本", "pm.expect(__json.data.csv).to.be.a('string').and.not.empty"),
                    test("CSV 包含中文表头", "pm.expect(__json.data.csv).to.include('学校ID'); pm.expect(__json.data.csv).to.include('处理措施')"),
                ],
            },
            {
                "name": "处置证照异常（DBCOMPAT-0024 修复后启用）",
                "method": "PATCH",
                "path": "/reg/credentials/exceptions/measure?id={{credential_exception_id}}",
                "body": {"measure": "已联系学校补充健康证复审材料，限2026-09-28前完成。"},
                "disabled": True,
                "tests": [test("异常处置返回成功", "pm.expect(__json.data.ok).to.equal(true)")],
            },
        ],
    },
    "R05-监管单位角色权限（双库真实写入）": {
        "prefix": "APIFOX_R05",
        "description": (
            "监管单位、角色、权限和人员双库真实写入。使用业务化中文名称创建并保留数据，"
            "通过列表和详情回查；监管人员 displayName 编辑因 DBCOMPAT-0025 默认关闭，"
            "监管机构信息写入因 DBCOMPAT-0026 不计入通过。"
        ),
        "operations": [
            {
                "name": "创建校园食品安全监管单位",
                "method": "POST",
                "path": "/reg/system/units",
                "body": {
                    "name": "朝阳区校园食品安全监管一组（{{test_batch}}）",
                    "address": "北京市朝阳区食品安全监管服务中心",
                    "contact": "周建国",
                    "phone": "010-65081234",
                    "regions": ["朝阳区"],
                    "schoolIds": ["1", "6"],
                    "enabled": True,
                },
                "extract": ("reg_unit_id", "$.data.id"),
                "tests": [test("监管单位返回有效 ID", "pm.expect(Number(__json.data.id)).to.be.greaterThan(0)")],
            },
            {
                "name": "更新监管单位联系人",
                "method": "PATCH",
                "path": "/reg/system/units?id={{reg_unit_id}}",
                "body": {"contact": "周建国（复核）", "enabled": True},
                "tests": [test("监管单位更新成功", "pm.expect(__json.data.ok).to.equal(true)")],
            },
            {
                "name": "回查监管单位",
                "method": "GET",
                "path": "/reg/system/units?page=1&pageSize=20&q={{test_batch}}",
                "tests": [
                    test("回查仅命中本轮单位", "pm.expect(__json.data.items).to.be.an('array').that.is.not.empty; const row=__json.data.items.find(function(x){return Number(x.id)===Number(pm.variables.get('reg_unit_id'));}); pm.expect(row).to.exist"),
                    test("单位中文与 JSON 字段未丢失", "const row=__json.data.items.find(function(x){return Number(x.id)===Number(pm.variables.get('reg_unit_id'));}); pm.expect(row.contact).to.equal('周建国（复核）'); pm.expect(row.regions).to.include('朝阳区'); pm.expect(row.schoolIds.map(String)).to.include('6')"),
                ],
            },
            {
                "name": "创建校园食品安全复核角色",
                "method": "POST",
                "path": "/reg/system/roles",
                "body": {"name": "校园食品安全复核员（{{test_batch}}）", "remark": "负责学校日常检查任务复核与证照异常跟踪"},
                "extract": ("reg_role_id", "$.data.id"),
                "tests": [test("监管角色返回有效 ID", "pm.expect(Number(__json.data.id)).to.be.greaterThan(0)")],
            },
            {
                "name": "绑定监管角色权限",
                "method": "POST",
                "path": "/reg/system/roles/permissions",
                "body": {"name": "校园食品安全复核员（{{test_batch}}）", "permissions": ["report:R", "credentials:R"]},
                "tests": [test("角色权限绑定成功", "pm.expect(__json.data.ok).to.equal(true)")],
            },
            {
                "name": "回查监管角色和权限",
                "method": "GET",
                "path": "/reg/system/roles",
                "tests": [
                    test("列表包含本轮角色", "const row=__json.data.find(function(x){return Number(x.id)===Number(pm.variables.get('reg_role_id'));}); pm.expect(row).to.exist; pm.expect(row.name).to.equal('校园食品安全复核员（'+pm.variables.get('test_batch')+'）')"),
                    test("角色权限真实读回", "const row=__json.data.find(function(x){return Number(x.id)===Number(pm.variables.get('reg_role_id'));}); pm.expect(row.permissions).to.include('report:R'); pm.expect(row.permissions).to.include('credentials:R')"),
                ],
            },
            {
                "name": "创建监管复核人员",
                "method": "POST",
                "path": "/reg/system/users",
                "body": {
                    "username": "cyspaq_{{test_batch}}",
                    "displayName": "赵明远",
                    "roles": ["校园食品安全复核员（{{test_batch}}）"],
                    "enabled": True,
                    "unitId": "{{reg_unit_id}}",
                },
                "extract": ("reg_user_id", "$.data.id"),
                "tests": [test("监管人员返回有效 ID", "pm.expect(Number(__json.data.id)).to.be.greaterThan(0)")],
            },
            {
                "name": "回查监管人员详情",
                "method": "GET",
                "path": "/reg/system/users/detail?id={{reg_user_id}}",
                "tests": [
                    test("监管人员中文姓名正确", "pm.expect(__json.data.displayName).to.equal('赵明远')"),
                    test("监管人员单位和角色正确", "pm.expect(Number(__json.data.unitId)).to.equal(Number(pm.variables.get('reg_unit_id'))); pm.expect(__json.data.roles).to.include('校园食品安全复核员（'+pm.variables.get('test_batch')+'）')"),
                ],
            },
            {
                "name": "编辑监管人员姓名（DBCOMPAT-0025 修复后启用）",
                "method": "PATCH",
                "path": "/reg/system/users?id={{reg_user_id}}",
                "body": {"displayName": "赵明远（复核岗）", "enabled": True},
                "disabled": True,
                "tests": [test("编辑后的姓名真实返回", "pm.expect(__json.data.ok).to.equal(true)")],
            },
        ],
    },
    "R06-系统配置台账报表（双库真实数据）": {
        "prefix": "APIFOX_R06",
        "description": (
            "监管系统设置、业务台账、日报、餐厨垃圾、AI 与明厨亮灶配置双库回归。"
            "系统设置只幂等写回产品原有名称并读回，禁止追加测试标记或改变页面展示；"
            "DBCOMPAT-0023 的 404 路由和 "
            "DBCOMPAT-0026 的进程内监管机构信息暂不计入通过。"
        ),
        "operations": [
            {"name": "检查项系统配置", "method": "GET", "path": "/reg/inspections/config", "tests": [test("检查配置结构完整", "pm.expect(__json.data).to.include.all.keys('items','penalties','publications')")]},
            {"name": "留样台账", "method": "GET", "path": "/reg/ledgers/sampling?page=1&pageSize=20", "tests": [test("留样台账分页完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(Number(__json.data.total)).to.be.at.least(__json.data.items.length)")]},
            {"name": "消毒台账", "method": "GET", "path": "/reg/ledgers/disinfection?page=1&pageSize=20", "tests": [test("消毒台账分页完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(Number(__json.data.total)).to.be.at.least(__json.data.items.length)")]},
            {"name": "陪餐台账", "method": "GET", "path": "/reg/ledgers/dine?page=1&pageSize=20", "tests": [test("陪餐台账分页完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(Number(__json.data.total)).to.be.at.least(__json.data.items.length)")]},
            {"name": "废弃物台账", "method": "GET", "path": "/reg/ledgers/waste?page=1&pageSize=20", "tests": [test("废弃物台账分页完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(Number(__json.data.total)).to.be.at.least(__json.data.items.length)")]},
            {"name": "农残台账", "method": "GET", "path": "/reg/ledgers/pesticide?page=1&pageSize=20", "tests": [test("农残台账分页完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(Number(__json.data.total)).to.be.at.least(__json.data.items.length)")]},
            {"name": "台账完整率统计", "method": "GET", "path": "/reg/ledgers/stats", "tests": [test("台账统计结构完整", "pm.expect(__json.data).to.include.all.keys('completeness','typeDist')")]},
            {"name": "台账上报统计", "method": "GET", "path": "/reg/ledgers/submit-stats?start=2026-09-20&end=2026-09-27", "tests": [test("上报统计包含学校和食堂", "pm.expect(__json.data).to.include.all.keys('items','schools','canteens')")]},
            {"name": "台账数据统计", "method": "GET", "path": "/reg/ledgers/data-stats?start=2026-09-20&end=2026-09-27", "tests": [test("数据统计包含明细和学校维度", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(__json.data.bySchool).to.be.an('array')")]},
            {"name": "台账统一搜索", "method": "GET", "path": "/reg/ledgers/search?page=1&pageSize=20", "tests": [test("统一搜索结构完整", "pm.expect(__json.data).to.include.all.keys('items','total','page','pageSize','completeness','bySchool')")]},
            {"name": "监管日报", "method": "GET", "path": "/reg/reports/daily?start=2026-09-20&end=2026-09-27", "tests": [test("日报包含行和汇总", "pm.expect(__json.data.rows).to.be.an('array'); pm.expect(__json.data.summary).to.be.an('object')")]},
            {"name": "餐厨垃圾学校排名", "method": "GET", "path": "/reg/food-waste/ranking?start=2026-09-20&end=2026-09-27", "tests": [test("排名为真实数组", "pm.expect(__json.data).to.be.an('array')")]},
            {"name": "餐厨垃圾趋势", "method": "GET", "path": "/reg/food-waste/trend?start=2026-09-20&end=2026-09-27", "tests": [test("趋势为真实数组", "pm.expect(__json.data).to.be.an('array')")]},
            {"name": "监管预警配置", "method": "GET", "path": "/reg/alerts/config", "tests": [test("预警配置结构完整", "pm.expect(__json.data).to.include.all.keys('enabled','channels','rules')")]},
            {"name": "监管 AI 事件", "method": "GET", "path": "/reg/ai/events?page=1&pageSize=20", "tests": [test("AI 事件分页完整", "pm.expect(__json.data.items).to.be.an('array'); pm.expect(__json.data).to.include.all.keys('total','page','pageSize')")]},
            {"name": "AI 监管任务", "method": "GET", "path": "/reg/ai/tasks?page=1&pageSize=20", "tests": [test("AI 任务为数组", "pm.expect(__json.data).to.be.an('array')")]},
            {"name": "AI 广播日志", "method": "GET", "path": "/reg/ai/broadcast/logs", "tests": [test("广播日志为数组", "pm.expect(__json.data).to.be.an('array')")]},
            {"name": "AI 分析方式", "method": "GET", "path": "/reg/ai/methods", "tests": [test("AI 方式为非空数组", "pm.expect(__json.data).to.be.an('array').that.is.not.empty")]},
            {"name": "AI 学校范围", "method": "GET", "path": "/reg/ai/schools", "tests": [test("AI 学校范围为非空数组", "pm.expect(__json.data).to.be.an('array').that.is.not.empty")]},
            {"name": "明厨亮灶摄像头配置", "method": "GET", "path": "/bright/reg/cameras", "tests": [test("摄像头配置为真实数组", "pm.expect(__json.data).to.be.an('array')")]},
            {
                "name": "幂等保存监管系统原有基础配置（不得改产品名）",
                "method": "POST",
                "path": "/reg/system/settings",
                "body": {"basic": {"systemName": "食品安全监管云", "logo": "", "themeColor": "#1677FF"}},
                "tests": [test("原有系统名称幂等写回成功", "pm.expect(__json.data.ok).to.equal(true)")],
            },
            {
                "name": "读回并确认系统名称未被测试用例改变",
                "method": "GET",
                "path": "/reg/system/settings",
                "tests": [test("系统名称保持产品原值", "pm.expect(__json.data.basic.systemName).to.equal('食品安全监管云'); pm.expect(__json.data.basic.themeColor).to.equal('#1677FF')")],
            },
        ],
    },
}


def tree(api: Any) -> dict[str, Any]:
    return api.call(f"/api/v1/projects/{PROJECT_ID}/test-scenario/tree-list")["data"]


def folder_id(api: Any) -> int:
    folder = next(
        (item for item in tree(api).get("testScenarioFolders", []) if item.get("name") == FOLDER_NAME),
        None,
    )
    if folder is None:
        raise RuntimeError(f"Apifox 缺少场景目录：{FOLDER_NAME}")
    return int(folder["id"])


def backup(api: Any) -> Path:
    BACKUP_DIR.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    target = BACKUP_DIR / f"regulator-r00-r03-before-sync_{stamp}.json"
    payload = {
        "generatedAt": datetime.now().astimezone().isoformat(),
        "projectId": PROJECT_ID,
        "tree": tree(api),
        "testSuites": api.call(f"/api/v1/projects/{PROJECT_ID}/api-test/test-suites")["data"],
    }
    target.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    return target


def save_suite(api: Any, scenarios: list[tuple[str, int]]) -> dict[str, Any]:
    suites = api.call(f"/api/v1/projects/{PROJECT_ID}/api-test/test-suites")["data"]
    current = next((item for item in suites if item.get("name") == SUITE_NAME), None)
    items = [
        {
            "id": new_id(),
            "name": name,
            "type": "STATIC_TEST_SCENARIO",
            "testScenarios": [{"id": scenario_id, "options": {}}],
            "options": {},
        }
        for name, scenario_id in scenarios
    ]
    description = (
        "监管端 MySQL foodsafe_test／达梦 WWWWEB 双库一键串行回归。"
        "当前纳入 R00–R06，包含登录、首页、学校数据、预警、检查任务、资质证照、"
        "监管单位角色权限、业务台账、报表、AI 和系统配置；"
        "每步包含字段断言、可见返回摘要和脱敏 APIFOX_EVIDENCE。"
    )
    if current:
        suite = api.call(f"/api/v1/projects/{PROJECT_ID}/api-test/test-suites/{int(current['id'])}")["data"]
        suite.update({"name": SUITE_NAME, "description": description, "items": items})
        suite.setdefault("options", {})["runMode"] = "serial"
        api.call(f"/api/v1/projects/{PROJECT_ID}/api-test/test-suites/{int(suite['id'])}", "PUT", suite)
        return api.call(f"/api/v1/projects/{PROJECT_ID}/api-test/test-suites/{int(suite['id'])}")["data"]
    payload = {
        "name": SUITE_NAME,
        "description": description,
        "priority": 2,
        "ordering": 30,
        "folderId": 0,
        "items": items,
        "options": {"runMode": "serial", "runnerId": 0},
        "tags": [],
    }
    created = api.call(f"/api/v1/projects/{PROJECT_ID}/api-test/test-suites", "POST", payload)["data"]
    suite_id = int(created["id"] if isinstance(created, dict) else created)
    return api.call(f"/api/v1/projects/{PROJECT_ID}/api-test/test-suites/{suite_id}")["data"]


def main() -> None:
    api = Apifox()
    backup_path = backup(api)
    target_folder_id = folder_id(api)
    by_name = {item["name"]: item for item in api.scenarios()}
    source = by_name["S00-环境与认证（双库）"]
    source_data = api.scenario(int(source["id"]))
    loop_template = source_data["steps"][0]
    login_template = loop_template["children"][0]
    request_template = loop_template["children"][1]

    r00_name = "R00-监管认证与环境（双库）"
    current_r00 = by_name.get(r00_name)
    r00_id = int(current_r00["id"]) if current_r00 else api.duplicate(int(source["id"]))
    r00 = api.scenario(r00_id)
    r00.update(
        {
            "name": r00_name,
            "description": (
                f"监管端共享认证基线：MySQL foodsafe_test={MYSQL_BASE_URL}，"
                f"达梦 WWWWEB={DM_BASE_URL}。覆盖登录、当前用户、刷新、退出和注销后 401；"
                "Token 仅作运行时临时变量，报告自动脱敏。"
            ),
            "folderId": target_folder_id,
            "ordering": 10,
        }
    )
    api.save(r00)
    scenario_ids: list[tuple[str, int]] = [(r00_name, r00_id)]

    for ordering, (name, config) in enumerate(SCENARIOS.items(), start=2):
        current = by_name.get(name)
        scenario_id = int(current["id"]) if current else api.duplicate(int(source["id"]))
        scenario = api.scenario(scenario_id)
        scenario.update(
            {
                "name": name,
                "description": config["description"],
                "folderId": target_folder_id,
                "ordering": ordering * 10,
            }
        )
        loop = copy.deepcopy(loop_template)
        loop["id"] = new_id()
        loop["number"] = 1
        loop["children"] = [make_login(login_template, config["prefix"])]
        for number, operation in enumerate(config["operations"], start=2):
            step = make_request(request_template, **operation)
            step["number"] = number
            loop["children"].append(step)
        scenario["steps"] = [loop]
        api.save(scenario)
        saved = api.scenario(scenario_id)
        requests = flatten_requests(saved["steps"])
        if not all(request_has_auditable_evidence(request) for request in requests):
            raise RuntimeError(f"{name}: one or more requests lack auditable evidence")
        scenario_ids.append((name, scenario_id))
        print(f"{name}: id={scenario_id}, businessOperations={len(config['operations'])}, dualExecutions={len(config['operations']) * 2}")

    suite = save_suite(api, scenario_ids)
    print(f"backup={backup_path}")
    print(f"R00: id={r00_id}, dualExecutions=10")
    print(f"suite={suite['name']}, id={suite['id']}, scenarios={len(scenario_ids)}, runMode={suite.get('options', {}).get('runMode')}")


if __name__ == "__main__":
    main()
