#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：test_ui_context.py
模块描述：ExecutionContext 单元测试（语义化用例执行上下文）

覆盖：
- parameters 初始化渲染（{__RANDOM_NAME_8__} / 跨键 CTX 引用）
- {__CTX:key__} 替换 / 未知键保留
- {__NOW__} 内置生成器复用
- 嵌套 dict/list 递归渲染
- 无 "__" 字符串零改动（老用例兼容）
- 原模板不被修改

作者：czj
创建日期：2026-08-18
"""

import re
import sys
from datetime import datetime
from pathlib import Path

# 加入项目路径，确保可导入
sys.path.insert(0, str(Path(__file__).parent.parent))

from app.executors.ui_context import ExecutionContext


# ==================== parameters 初始化渲染 ====================


def test_parameters_random_name_rendered():
    """parameters 值中的 {__RANDOM_NAME_8__} 应在初始化时渲染为 8 位随机串"""
    ctx = ExecutionContext({"meetingName": "自动化-{__RANDOM_NAME_8__}"})
    value = ctx.get("meetingName")
    assert value.startswith("自动化-"), f"应保留前缀: {value}"
    suffix = value.split("-", 1)[1]
    assert len(suffix) == 8, f"后缀应为 8 位: {value}"
    assert suffix.isalnum(), f"后缀应为字母数字: {value}"


def test_parameters_cross_key_reference():
    """参数键之间的 {__CTX:...__} 交叉引用应迭代收敛"""
    ctx = ExecutionContext({
        "base": "自动化-{__RANDOM_NAME_8__}",
        "title": "主题-{__CTX:base__}",
    })
    title = ctx.get("title")
    base = ctx.get("base")
    assert title == f"主题-{base}", f"交叉引用未收敛: {title} != 主题-{base}"


def test_parameters_forward_reference():
    """前向引用（{__CTX:B__} 引用定义在后面的键 B）应跨轮收敛"""
    ctx = ExecutionContext({
        "name": "会议-{__CTX:uid__}",
        "uid": "U-{__RANDOM_NAME_4__}",
    })
    uid = ctx.get("uid")
    assert ctx.get("name") == f"会议-{uid}", f"前向引用未收敛: name={ctx.get('name')} != 会议-{uid}"


def test_parameters_plain_value_untouched():
    """普通值（无占位符）应原样保留"""
    ctx = ExecutionContext({"room": "北京展厅会议室", "participant": "admin@xty12"})
    assert ctx.get("room") == "北京展厅会议室"
    assert ctx.get("participant") == "admin@xty12"


# ==================== {__CTX:key__} 替换 ====================


def test_ctx_placeholder_substituted():
    """{__CTX:meetingName__} 应替换为参数值"""
    ctx = ExecutionContext({"meetingName": "自动化-test"})
    result = ctx.render("{__CTX:meetingName__}")
    assert result == "自动化-test"


def test_ctx_embedded_in_text():
    """{__CTX:key__} 可嵌入任意字符串"""
    ctx = ExecutionContext({"meetingName": "自动化-test"})
    result = ctx.render("会议《{__CTX:meetingName__}》的议题")
    assert result == "会议《自动化-test》的议题"


def test_ctx_unknown_key_preserved():
    """未知键应保留占位符原文"""
    ctx = ExecutionContext({})
    result = ctx.render("{__CTX:not_exists__}")
    assert result == "{__CTX:not_exists__}"


def test_ctx_after_set():
    """save_as 写入的变量应可被后续渲染引用"""
    ctx = ExecutionContext({})
    ctx.set("createdId", "meeting_123")
    assert ctx.get("createdId") == "meeting_123"
    result = ctx.render("ID={__CTX:createdId__}")
    assert result == "ID=meeting_123"


# ==================== 内置生成器复用 ====================


def test_now_generator_reused():
    """{__NOW__} 应复用 TemplateResolver 内置生成器"""
    ctx = ExecutionContext({})
    result = ctx.render({"time": "{__NOW__}"})
    now_prefix = datetime.now().strftime("%Y-%m-%d %H:%M:%S")[:16]
    assert result["time"].startswith(now_prefix), f"{result['time']} != {now_prefix}"


def test_builtin_and_ctx_combined():
    """内置生成器与 CTX 可混合使用"""
    ctx = ExecutionContext({"tag": "summit"})
    result = ctx.render("{__CTX:tag__}-{__RANDOM_NAME_4__}")
    assert result.startswith("summit-")
    assert len(result) == len("summit-") + 4


# ==================== 嵌套渲染 ====================


def test_nested_dict_list_rendered():
    """嵌套 dict/list 应递归渲染"""
    ctx = ExecutionContext({"meetingName": "自动化-test", "participant": "admin@xty12"})
    template = {
        "body": {
            "name": "{__CTX:meetingName__}",
            "participants": ["{__CTX:participant__}"],
            "meta": {"seq": "{__RANDOM_INT_1_100__}"},
        },
    }
    result = ctx.render(template)
    assert result["body"]["name"] == "自动化-test"
    assert result["body"]["participants"] == ["admin@xty12"]
    assert 1 <= int(result["body"]["meta"]["seq"]) <= 100


# ==================== 兼容性（老用例零影响） ====================


def test_no_placeholder_unchanged():
    """不含 '__' 的字符串应原样返回（老用例零影响）"""
    ctx = ExecutionContext({"meetingName": "自动化-test"})
    params = {"selector": "#submit", "value": "固定值", "url": "/login"}
    result = ctx.render(params)
    assert result == params


def test_original_template_not_modified():
    """渲染返回新容器，原模板不被修改（防重复渲染放大动态值）"""
    ctx = ExecutionContext({})
    params = {"value": "{__RANDOM_NAME_8__}"}
    result = ctx.render(params)
    assert result["value"] != params["value"]
    assert params["value"] == "{__RANDOM_NAME_8__}", "原模板不应被修改"


def test_empty_render():
    """空容器渲染应返回空"""
    ctx = ExecutionContext({})
    assert ctx.render({}) == {}
    assert ctx.render([]) == []
    assert ctx.render("") == ""
    assert ctx.render(42) == 42
    assert ctx.render(None) is None
