#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：test_template_resolver.py
模块描述：TemplateResolver 单元测试

覆盖 8 种占位符 + INDEX 偏移新语法

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

import re
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path

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

from app.executors.template_resolver import TemplateResolver


def test_now_placeholder():
    """{__NOW__} 应替换为当前时间字符串"""
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW__}"}, 0)
    now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    # 允许秒级误差
    assert result["time"].startswith(now_str[:16]), f"{result['time']} != {now_str}"


def test_now_plus_hours():
    """{__NOW_+2_H__} 应替换为当前时间 + 2 小时"""
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW_+2_H__}"}, 0)
    expected = (datetime.now() + timedelta(hours=2)).strftime("%Y-%m-%d %H:%M:%S")
    assert result["time"].startswith(expected[:16]), f"{result['time']} != {expected}"


def test_now_plus_minutes():
    """{__NOW_+30_M__} 应替换为当前时间 + 30 分钟"""
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW_+30_M__}"}, 0)
    expected = (datetime.now() + timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M:%S")
    assert result["time"].startswith(expected[:16]), f"{result['time']} != {expected}"


def test_now_plus_days():
    """{__NOW_+1_D__} 应替换为当前时间 + 1 天"""
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW_+1_D__}"}, 0)
    expected = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S")
    assert result["time"].startswith(expected[:16]), f"{result['time']} != {expected}"


# ==================== INDEX 偏移测试 ====================


def test_now_index_offset_request_0():
    """
    {__NOW_+37_H_INDEX__} request_index=0
    预期：当前时间 + 37h + 0min
    """
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW_+37_H_INDEX__}"}, 0)
    expected = (datetime.now() + timedelta(hours=37)).strftime("%Y-%m-%d %H:%M:%S")
    assert result["time"].startswith(expected[:16]), f"request_index=0: {result['time']} != {expected}"


def test_now_index_offset_request_5():
    """
    {__NOW_+37_H_INDEX__} request_index=5
    预期：当前时间 + 37h + 5min
    """
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW_+37_H_INDEX__}"}, 5)
    expected = (datetime.now() + timedelta(hours=37, minutes=5)).strftime("%Y-%m-%d %H:%M:%S")
    assert result["time"].startswith(expected[:16]), f"request_index=5: {result['time']} != {expected}"


def test_now_index_offset_difference():
    """
    同一模板，request_index=0 与 request_index=5 的差值应为 5 分钟
    """
    resolver = TemplateResolver()
    # 连续调用，确保在相同秒内执行
    r0 = resolver.resolve({"time": "{__NOW_+37_H_INDEX__}"}, 0)["time"]
    r5 = resolver.resolve({"time": "{__NOW_+37_H_INDEX__}"}, 5)["time"]

    dt0 = datetime.strptime(r0, "%Y-%m-%d %H:%M:%S")
    dt5 = datetime.strptime(r5, "%Y-%m-%d %H:%M:%S")
    diff = (dt5 - dt0).total_seconds()
    assert 295 <= diff <= 305, f"差值应为 300s（5min），实际 {diff}s"


def test_now_bare_index():
    """
    {__NOW_INDEX__} request_index=3
    预期：当前时间 + 3min
    """
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW_INDEX__}"}, 3)
    expected = (datetime.now() + timedelta(minutes=3)).strftime("%Y-%m-%d %H:%M:%S")
    assert result["time"].startswith(expected[:16]), f"NOW_INDEX 3: {result['time']} != {expected}"


def test_now_index_minutes_unit():
    """
    {__NOW_+30_M_INDEX__} request_index=2
    预期：当前时间 + 30min + 2min = 32min
    """
    resolver = TemplateResolver()
    result = resolver.resolve({"time": "{__NOW_+30_M_INDEX__}"}, 2)
    expected = (datetime.now() + timedelta(minutes=32)).strftime("%Y-%m-%d %H:%M:%S")
    assert result["time"].startswith(expected[:16]), f"NOW_+30_M_INDEX 2: {result['time']} != {expected}"


def test_now_without_index_unchanged():
    """
    {__NOW_+37_H__}（无 INDEX 后缀）行为不变
    验证：request_index=0 与 request_index=5 结果相同（无 INDEX 时忽略 index）
    """
    resolver = TemplateResolver()
    r0 = resolver.resolve({"time": "{__NOW_+37_H__}"}, 0)["time"]
    r5 = resolver.resolve({"time": "{__NOW_+37_H__}"}, 5)["time"]
    # 允许 1 秒误差（两次调用时间差）
    dt0 = datetime.strptime(r0, "%Y-%m-%d %H:%M:%S")
    dt5 = datetime.strptime(r5, "%Y-%m-%d %H:%M:%S")
    diff = abs((dt5 - dt0).total_seconds())
    assert diff <= 1, f"无 INDEX 时应忽略 index，差值 {diff}s > 1s"


def test_now_unknown_suffix_ignored():
    """
    {__NOW_+37_H_X__}（未知后缀）应忽略后缀，行为与无 INDEX 等价
    """
    resolver = TemplateResolver()
    r0 = resolver.resolve({"time": "{__NOW_+37_H_X__}"}, 0)["time"]
    r5 = resolver.resolve({"time": "{__NOW_+37_H_X__}"}, 5)["time"]
    # 未知后缀不触发偏移，两次结果应接近
    dt0 = datetime.strptime(r0, "%Y-%m-%d %H:%M:%S")
    dt5 = datetime.strptime(r5, "%Y-%m-%d %H:%M:%S")
    diff = abs((dt5 - dt0).total_seconds())
    assert diff <= 1, f"未知后缀 'X' 应忽略，差值 {diff}s > 1s"


# ==================== 其他占位符测试 ====================


def test_random_name():
    """{__RANDOM_NAME_8__} 应生成 8 位随机字符串"""
    resolver = TemplateResolver()
    result = resolver.resolve({"name": "{__RANDOM_NAME_8__}"}, 0)
    assert len(result["name"]) == 8, f"长度应为 8，实际 {len(result['name'])}"
    assert result["name"].isalnum(), f"应为字母数字，实际 {result['name']}"


def test_random_name_twice_different():
    """连续两次 RANDOM_NAME 应生成不同值"""
    resolver = TemplateResolver()
    r1 = resolver.resolve({"name": "{__RANDOM_NAME_8__}"}, 0)["name"]
    r2 = resolver.resolve({"name": "{__RANDOM_NAME_8__}"}, 0)["name"]
    assert r1 != r2, f"两次随机值应不同：{r1} == {r2}"


def test_uuid():
    """{__UUID__} 应生成 32 位十六进制字符串（无横线）"""
    resolver = TemplateResolver()
    result = resolver.resolve({"id": "{__UUID__}"}, 0)
    assert len(result["id"]) == 32, f"UUID 长度应为 32，实际 {len(result['id'])}"
    assert all(c in "0123456789abcdef" for c in result["id"]), "UUID 应为十六进制"


def test_random_int():
    """{__RANDOM_INT_1_100__} 应生成 1~100 之间的整数"""
    resolver = TemplateResolver()
    for _ in range(50):
        result = resolver.resolve({"val": "{__RANDOM_INT_1_100__}"}, 0)
        val = int(result["val"])
        assert 1 <= val <= 100, f"值应在 1~100 之间，实际 {val}"


def test_index():
    """{__INDEX__} 应返回 request_index 值"""
    resolver = TemplateResolver()
    result = resolver.resolve({"idx": "{__INDEX__}"}, 42)
    assert result["idx"] == "42", f"应为 '42'，实际 {result['idx']}"


def test_index_zero():
    """{__INDEX__} 在 request_index=0 时应返回 '0'"""
    resolver = TemplateResolver()
    result = resolver.resolve({"idx": "{__INDEX__}"}, 0)
    assert result["idx"] == "0", f"应为 '0'，实际 {result['idx']}"


# ==================== 嵌套模板测试 ====================


def test_nested_template():
    """嵌套 dict/list 模板应递归替换所有占位符"""
    resolver = TemplateResolver()
    template = {
        "meeting": {
            "name": "会议_{__RANDOM_NAME_4__}",
            "time": {
                "start": "{__NOW_+37_H_INDEX__}",
                "end": "{__NOW_+39_H_INDEX__}",
            },
        },
        "participants": [
            {"uid": "{__UUID__}", "role": "admin"},
        ],
    }
    result = resolver.resolve(template, 3)
    assert "RANDOM_NAME" not in result["meeting"]["name"], f"占位符未替换: {result['meeting']['name']}"
    assert result["meeting"]["time"]["start"].startswith(
        (datetime.now() + timedelta(hours=37, minutes=3)).strftime("%Y-%m-%d %H:%M")[:16]
    ), f"startTime 偏移错误: {result['meeting']['time']['start']}"
    assert len(result["participants"][0]["uid"]) == 32, "UUID 长度错误"


# ==================== 未知占位符测试 ====================


def test_unknown_placeholder():
    """未知占位符应保留原样"""
    resolver = TemplateResolver()
    result = resolver.resolve({"val": "{__UNKNOWN__}"}, 0)
    assert result["val"] == "{__UNKNOWN__}", f"应保留原样，实际 {result['val']}"


# ==================== 空/边界测试 ====================


def test_empty_template():
    """空模板应返回空"""
    resolver = TemplateResolver()
    assert resolver.resolve({}, 0) == {}
    assert resolver.resolve([], 0) == []
    assert resolver.resolve("", 0) == ""


def test_no_placeholder():
    """无占位符的字符串应原样返回"""
    resolver = TemplateResolver()
    result = resolver.resolve({"name": "固定名称"}, 0)
    assert result["name"] == "固定名称"


def test_plain_value_types():
    """非字符串类型（数字、布尔、None）应原样返回"""
    resolver = TemplateResolver()
    result = resolver.resolve({"int": 42, "float": 3.14, "none": None, "bool": True}, 0)
    assert result["int"] == 42
    assert result["float"] == 3.14
    assert result["none"] is None
    assert result["bool"] is True