# -*- coding: utf-8 -*-
"""TaskRegistry 属性测试 — 使用 hypothesis 验证注册表的通用正确性属性。"""
import string

import pytest
from hypothesis import given, settings
from hypothesis import strategies as st

from orchestration.task_registry import TaskRegistry, TaskMeta


# ── 辅助：动态生成假任务类 ────────────────────────────────────

def _make_fake_class(name: str = "FakeTask") -> type:
    """创建一个最小化的假任务类，用于注册测试。"""
    return type(name, (), {"__init__": lambda self, *a, **kw: None})


# ── 生成策略 ──────────────────────────────────────────────────

# 合法任务代码：大写字母 + 数字 + 下划线，长度 1~30
task_code_st = st.text(
    alphabet=string.ascii_uppercase + string.digits + "_",
    min_size=1,
    max_size=30,
)

requires_db_config_st = st.booleans()

layer_st = st.sampled_from([None, "ODS", "DWD", "DWS", "INDEX"])

task_type_st = st.sampled_from(["etl", "utility", "verification"])


# ── Property 8: TaskRegistry 元数据 round-trip ────────────────
# Feature: scheduler-refactor, Property 8: TaskRegistry 元数据 round-trip
# **Validates: Requirements 4.1**
#
# 对于任意任务代码、任务类和元数据组合（requires_db_config、layer、task_type），
# 注册后通过 get_metadata 查询应返回相同的元数据值。


class TestProperty8MetadataRoundTrip:
    """Property 8: 注册元数据后查询应返回完全相同的值。"""

    @given(
        task_code=task_code_st,
        requires_db=requires_db_config_st,
        layer=layer_st,
        task_type=task_type_st,
    )
    @settings(max_examples=100)
    def test_metadata_round_trip(
        self, task_code: str, requires_db: bool, layer: str | None, task_type: str
    ):
        """注册任意元数据组合后，get_metadata 应返回相同的值。"""
        # Arrange — 每次迭代使用全新的注册表，避免状态泄漏
        registry = TaskRegistry()
        fake_cls = _make_fake_class()

        # Act — 注册并查询
        registry.register(
            task_code,
            fake_cls,
            requires_db_config=requires_db,
            layer=layer,
            task_type=task_type,
        )
        meta = registry.get_metadata(task_code)

        # Assert — 元数据 round-trip 一致
        assert meta is not None, f"注册后 get_metadata('{task_code}') 不应返回 None"
        assert meta.task_class is fake_cls, "task_class 应与注册时一致"
        assert meta.requires_db_config is requires_db, (
            f"requires_db_config 应为 {requires_db}，实际为 {meta.requires_db_config}"
        )
        assert meta.layer == layer, f"layer 应为 {layer!r}，实际为 {meta.layer!r}"
        assert meta.task_type == task_type, (
            f"task_type 应为 {task_type!r}，实际为 {meta.task_type!r}"
        )


# ── Property 9: TaskRegistry 向后兼容默认值 ───────────────────
# Feature: scheduler-refactor, Property 9: TaskRegistry 向后兼容默认值
# **Validates: Requirements 4.4**
#
# 对于任意使用旧接口（仅 task_code 和 task_class）注册的任务，
# 查询元数据应返回 requires_db_config=True、layer=None、task_type="etl"。


class TestProperty9BackwardCompatibleDefaults:
    """Property 9: 仅传 task_code + task_class 时，元数据应使用默认值。"""

    @given(task_code=task_code_st)
    @settings(max_examples=100)
    def test_legacy_register_uses_defaults(self, task_code: str):
        """使用旧接口（仅 task_code 和 task_class）注册后，元数据应为默认值。"""
        # Arrange
        registry = TaskRegistry()
        fake_cls = _make_fake_class()

        # Act — 仅传 task_code 和 task_class，不传任何元数据参数
        registry.register(task_code, fake_cls)
        meta = registry.get_metadata(task_code)

        # Assert — 默认值契约
        assert meta is not None, f"注册后 get_metadata('{task_code}') 不应返回 None"
        assert meta.task_class is fake_cls, "task_class 应与注册时一致"
        assert meta.requires_db_config is True, (
            f"默认 requires_db_config 应为 True，实际为 {meta.requires_db_config}"
        )
        assert meta.layer is None, (
            f"默认 layer 应为 None，实际为 {meta.layer!r}"
        )
        assert meta.task_type == "etl", (
            f"默认 task_type 应为 'etl'，实际为 {meta.task_type!r}"
        )


# ── Property 10: 按层查询任务 ────────────────────────────────
# Feature: scheduler-refactor, Property 10: 按层查询任务
# **Validates: Requirements 4.3**
#
# 对于任意注册了 layer 元数据的任务集合，get_tasks_by_layer(layer)
# 返回的任务代码集合应等于所有 layer 匹配的已注册任务代码集合。

# 非 None 的层值策略，用于查询验证
non_none_layer_st = st.sampled_from(["ODS", "DWD", "DWS", "INDEX"])


class TestProperty10GetTasksByLayer:
    """Property 10: get_tasks_by_layer 返回的集合应与手动过滤一致。"""

    @given(
        entries=st.lists(
            st.tuples(task_code_st, layer_st),
            min_size=1,
            max_size=20,
        ),
    )
    @settings(max_examples=100)
    def test_get_tasks_by_layer_matches_manual_filter(
        self, entries: list[tuple[str, str | None]],
    ):
        """注册一组任务后，按层查询结果应与手动过滤完全一致。"""
        # Arrange
        registry = TaskRegistry()
        # 去重：同一 task_code 只保留最后一次注册（与 register 覆盖语义一致）
        unique_entries: dict[str, str | None] = {}
        for code, layer in entries:
            fake_cls = _make_fake_class(f"Fake_{code}")
            registry.register(code, fake_cls, layer=layer)
            unique_entries[code.upper()] = layer  # register 内部会 upper()

        # Act & Assert — 对每个非 None 的层值进行验证
        for query_layer in ["ODS", "DWD", "DWS", "INDEX"]:
            actual = set(registry.get_tasks_by_layer(query_layer))
            expected = {
                code for code, layer in unique_entries.items()
                if layer is not None and layer.upper() == query_layer.upper()
            }
            assert actual == expected, (
                f"查询 layer={query_layer!r} 时，"
                f"期望 {expected}，实际 {actual}"
            )
