# -*- coding: utf-8 -*-
"""Property 6 + 单元测试：_resolve_tasks() 配置优先级

# Feature: etl-dws-flow-refactor, Property 6: 配置优先级
# 验证：配置值优先于 Registry；配置为空时回退到 Registry；两者皆空返回空列表。
"""
import logging
from unittest.mock import MagicMock

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

from orchestration.flow_runner import FlowRunner

# ---------------------------------------------------------------------------
# 辅助：构造最小可用的 FlowRunner 实例
# ---------------------------------------------------------------------------

_LAYERS = ["ODS", "DWD", "DWS", "INDEX"]

# 配置键映射（与 _resolve_tasks 内部一致）
_LAYER_CONFIG_KEY = {
    "ODS": "run.ods_tasks",
    "DWD": "run.dwd_tasks",
    "DWS": "run.dws_tasks",
    "INDEX": "run.index_tasks",
}


class _DictConfig:
    """模拟 AppConfig.get()，支持点号路径查找。"""

    def __init__(self, data: dict):
        self._data = data

    def get(self, key: str, default=None):
        keys = key.split(".")
        node = self._data
        for k in keys:
            if isinstance(node, dict) and k in node:
                node = node[k]
            else:
                return default
        return node


def _make_runner(config_data: dict, registry_by_layer: dict[str, list[str]]) -> FlowRunner:
    """构造一个最小 FlowRunner，仅满足 _resolve_tasks 所需依赖。"""
    config = _DictConfig(config_data)

    registry = MagicMock()
    registry.get_tasks_by_layer = MagicMock(
        side_effect=lambda layer: registry_by_layer.get(layer.upper(), [])
    )

    runner = object.__new__(FlowRunner)
    runner.config = config
    runner.task_registry = registry
    runner.logger = logging.getLogger("test_resolve_tasks")
    return runner


# ---------------------------------------------------------------------------
# Hypothesis 策略
# ---------------------------------------------------------------------------

# 任务代码：大写蛇形，1~3 段
_task_code = st.from_regex(r"[A-Z][A-Z0-9_]{2,20}", fullmatch=True)

_task_list = st.lists(_task_code, min_size=1, max_size=8, unique=True)

_layer = st.sampled_from(_LAYERS)


# ---------------------------------------------------------------------------
# Property 6: 配置优先级——配置值优先于 Registry
# Feature: etl-dws-flow-refactor, Property 6: 配置优先级
# ---------------------------------------------------------------------------

class TestResolveTasksProperty:
    """Property 6: 配置优先级——配置值优先于 Registry"""

    @given(layer=_layer, config_tasks=_task_list, registry_tasks=_task_list)
    @settings(max_examples=100)
    def test_config_overrides_registry(
        self, layer: str, config_tasks: list[str], registry_tasks: list[str]
    ):
        """当配置非空时，_resolve_tasks 返回配置中的任务，忽略 Registry。

        **Validates: Requirements 7.2**
        """
        # 确保配置和 Registry 不同，以区分来源
        assume(set(config_tasks) != set(registry_tasks))

        config_key = _LAYER_CONFIG_KEY[layer]
        # 构造嵌套 dict：如 "run.ods_tasks" → {"run": {"ods_tasks": [...]}}
        parts = config_key.split(".")
        config_data = {parts[0]: {parts[1]: config_tasks}}

        runner = _make_runner(config_data, {layer: registry_tasks})
        result = runner._resolve_tasks([layer])

        assert result == config_tasks

    @given(layer=_layer, registry_tasks=_task_list)
    @settings(max_examples=100)
    def test_empty_config_falls_back_to_registry(
        self, layer: str, registry_tasks: list[str]
    ):
        """当配置为空时，_resolve_tasks 回退到 Registry 结果。

        **Validates: Requirements 7.2**
        """
        runner = _make_runner({}, {layer: registry_tasks})
        result = runner._resolve_tasks([layer])

        assert result == registry_tasks

    @given(layer=_layer)
    @settings(max_examples=100)
    def test_both_empty_returns_empty(self, layer: str):
        """当配置和 Registry 均为空时，返回空列表。

        **Validates: Requirements 7.2**
        """
        runner = _make_runner({}, {layer: []})
        result = runner._resolve_tasks([layer])

        assert result == []


# ---------------------------------------------------------------------------
# 单元测试：具体示例和边界条件
# ---------------------------------------------------------------------------

class TestResolveTasksUnit:
    """_resolve_tasks 配置优先级——具体示例"""

    def test_ods_config_overrides_registry(self):
        """ODS 层：配置指定任务时忽略 Registry。"""
        runner = _make_runner(
            {"run": {"ods_tasks": ["TASK_A", "TASK_B"]}},
            {"ODS": ["TASK_X", "TASK_Y"]},
        )
        assert runner._resolve_tasks(["ODS"]) == ["TASK_A", "TASK_B"]

    def test_dwd_config_overrides_registry(self):
        """DWD 层：配置指定任务时忽略 Registry。"""
        runner = _make_runner(
            {"run": {"dwd_tasks": ["DWD_CUSTOM"]}},
            {"DWD": ["DWD_LOAD_FROM_ODS"]},
        )
        assert runner._resolve_tasks(["DWD"]) == ["DWD_CUSTOM"]

    def test_dws_falls_back_to_registry(self):
        """DWS 层：无配置时回退到 Registry。"""
        registry_tasks = ["DWS_ASSISTANT_DAILY", "DWS_FINANCE_DAILY"]
        runner = _make_runner({}, {"DWS": registry_tasks})
        assert runner._resolve_tasks(["DWS"]) == registry_tasks

    def test_multiple_layers_mixed(self):
        """多层混合：部分层有配置，部分回退 Registry。"""
        runner = _make_runner(
            {"run": {"ods_tasks": ["ODS_CUSTOM"]}},
            {"ODS": ["ODS_REG"], "DWD": ["DWD_LOAD_FROM_ODS"]},
        )
        result = runner._resolve_tasks(["ODS", "DWD"])
        # ODS 走配置，DWD 走 Registry
        assert result == ["ODS_CUSTOM", "DWD_LOAD_FROM_ODS"]

    def test_empty_config_list_falls_back(self):
        """配置值为空列表时视为无配置，回退 Registry。"""
        runner = _make_runner(
            {"run": {"ods_tasks": []}},
            {"ODS": ["ODS_FROM_REGISTRY"]},
        )
        assert runner._resolve_tasks(["ODS"]) == ["ODS_FROM_REGISTRY"]

    def test_both_empty_returns_empty_list(self):
        """配置和 Registry 均为空时返回空列表。"""
        runner = _make_runner({}, {"INDEX": []})
        assert runner._resolve_tasks(["INDEX"]) == []

    def test_unknown_layer_returns_empty(self):
        """未知层名（无配置键映射）回退 Registry，Registry 也无则返回空。"""
        runner = _make_runner({}, {})
        result = runner._resolve_tasks(["UNKNOWN"])
        assert result == []
