# -*- coding: utf-8 -*-
"""拓扑排序单元测试

覆盖正常依赖、循环依赖、缺失依赖、空列表等场景。
属性测试（Property 7, 8）位于 Monorepo 级 tests/test_etl_refactor_properties.py。
"""
import logging
from dataclasses import dataclass, field
from unittest.mock import MagicMock

import pytest

from orchestration.topological_sort import topological_sort


# ---------------------------------------------------------------------------
# 辅助：轻量 TaskMeta 替身 + 简易 Registry
# ---------------------------------------------------------------------------

@dataclass
class _FakeMeta:
    depends_on: list[str] = field(default_factory=list)
    layer: str | None = None


class _FakeRegistry:
    """最小 Registry 替身，仅提供 get_metadata()。"""

    def __init__(self, deps: dict[str, list[str]], layers: dict[str, str] | None = None):
        self._deps = deps
        self._layers = layers or {}

    def get_metadata(self, code: str):
        if code in self._deps:
            return _FakeMeta(
                depends_on=self._deps[code],
                layer=self._layers.get(code),
            )
        if code in self._layers:
            return _FakeMeta(layer=self._layers[code])
        return _FakeMeta()


# ---------------------------------------------------------------------------
# 单元测试
# ---------------------------------------------------------------------------

class TestTopologicalSort:
    """拓扑排序——具体示例和边界条件"""

    def test_empty_list(self):
        """空任务列表返回空列表。"""
        reg = _FakeRegistry({})
        assert topological_sort([], reg) == []

    def test_single_task_no_deps(self):
        """单个无依赖任务原样返回。"""
        reg = _FakeRegistry({"A": []})
        assert topological_sort(["A"], reg) == ["A"]

    def test_linear_chain(self):
        """线性依赖链：A → B → C，排序后 A 在 B 前，B 在 C 前。"""
        reg = _FakeRegistry({"A": [], "B": ["A"], "C": ["B"]})
        result = topological_sort(["A", "B", "C"], reg)
        assert result.index("A") < result.index("B") < result.index("C")

    def test_diamond_dependency(self):
        """菱形依赖：A → B, A → C, B → D, C → D。"""
        reg = _FakeRegistry({"A": [], "B": ["A"], "C": ["A"], "D": ["B", "C"]})
        result = topological_sort(["A", "B", "C", "D"], reg)
        assert result.index("A") < result.index("B")
        assert result.index("A") < result.index("C")
        assert result.index("B") < result.index("D")
        assert result.index("C") < result.index("D")

    def test_no_dependencies_preserves_input_order(self):
        """无依赖关系时，保持输入顺序（Kahn's 的 FIFO 特性）。"""
        reg = _FakeRegistry({"X": [], "Y": [], "Z": []})
        result = topological_sort(["X", "Y", "Z"], reg)
        assert result == ["X", "Y", "Z"]

    def test_circular_dependency_raises(self):
        """循环依赖抛出 ValueError，错误信息包含涉及的任务。"""
        reg = _FakeRegistry({"A": ["B"], "B": ["A"]})
        with pytest.raises(ValueError, match="循环依赖"):
            topological_sort(["A", "B"], reg)

    def test_circular_three_tasks(self):
        """三任务环：A → B → C → A。"""
        reg = _FakeRegistry({"A": ["C"], "B": ["A"], "C": ["B"]})
        with pytest.raises(ValueError, match="循环依赖"):
            topological_sort(["A", "B", "C"], reg)

    def test_partial_cycle_reports_cycle_tasks(self):
        """部分任务成环时，错误信息仅包含环中的任务。"""
        # D 无依赖，A ↔ B 成环
        reg = _FakeRegistry({"D": [], "A": ["B"], "B": ["A"]})
        with pytest.raises(ValueError) as exc_info:
            topological_sort(["D", "A", "B"], reg)
        msg = str(exc_info.value)
        assert "A" in msg and "B" in msg

    def test_missing_dependency_logs_warning(self, caplog):
        """依赖任务不在执行列表中时记录警告但继续执行。"""
        reg = _FakeRegistry({"A": ["MISSING_TASK"], "B": []})
        with caplog.at_level(logging.WARNING):
            result = topological_sort(["A", "B"], reg)
        # 排序应正常完成
        assert set(result) == {"A", "B"}
        assert "MISSING_TASK" in caplog.text
        assert "不在当前执行列表中" in caplog.text

    def test_missing_dependency_does_not_affect_order(self):
        """缺失依赖不影响列表内任务的排序。"""
        # B 依赖 A（在列表内）和 EXTERNAL（不在列表内）
        reg = _FakeRegistry({"A": [], "B": ["A", "EXTERNAL"]})
        result = topological_sort(["A", "B"], reg)
        assert result.index("A") < result.index("B")

    def test_metadata_none_treated_as_no_deps(self):
        """get_metadata 返回 None 时视为无依赖。"""
        reg = MagicMock()
        reg.get_metadata.return_value = None
        result = topological_sort(["A", "B"], reg)
        assert set(result) == {"A", "B"}

    def test_real_dws_dependency_scenario(self):
        """模拟真实 DWS 依赖场景：MONTHLY 依赖 DAILY，MAINTENANCE 依赖所有。"""
        reg = _FakeRegistry({
            "DWS_ASSISTANT_DAILY": [],
            "DWS_ASSISTANT_MONTHLY": ["DWS_ASSISTANT_DAILY"],
            "DWS_FINANCE_DAILY": [],
            "DWS_MAINTENANCE": [
                "DWS_ASSISTANT_DAILY",
                "DWS_ASSISTANT_MONTHLY",
                "DWS_FINANCE_DAILY",
            ],
        })
        tasks = [
            "DWS_ASSISTANT_DAILY",
            "DWS_ASSISTANT_MONTHLY",
            "DWS_FINANCE_DAILY",
            "DWS_MAINTENANCE",
        ]
        result = topological_sort(tasks, reg)
        # DAILY 在 MONTHLY 前
        assert result.index("DWS_ASSISTANT_DAILY") < result.index("DWS_ASSISTANT_MONTHLY")
        # 所有任务在 MAINTENANCE 前
        assert result.index("DWS_MAINTENANCE") == len(result) - 1

    # ── 跨层隐含依赖测试 ──────────────────────────────────────

    def test_cross_layer_ods_before_dwd(self):
        """ODS 任务应排在 DWD 任务之前（隐含层级依赖）。"""
        reg = _FakeRegistry(
            deps={"ODS_A": [], "DWD_LOAD": []},
            layers={"ODS_A": "ODS", "DWD_LOAD": "DWD"},
        )
        # 故意把 DWD 放前面
        result = topological_sort(["DWD_LOAD", "ODS_A"], reg)
        assert result.index("ODS_A") < result.index("DWD_LOAD")

    def test_cross_layer_full_pipeline_order(self):
        """全流程：ODS → DWD → DWS → INDEX，无论输入顺序如何。"""
        reg = _FakeRegistry(
            deps={"IDX": [], "DWS_A": [], "DWD_L": [], "ODS_X": []},
            layers={"ODS_X": "ODS", "DWD_L": "DWD", "DWS_A": "DWS", "IDX": "INDEX"},
        )
        # 故意倒序输入
        result = topological_sort(["IDX", "DWS_A", "DWD_L", "ODS_X"], reg)
        assert result.index("ODS_X") < result.index("DWD_L")
        assert result.index("DWD_L") < result.index("DWS_A")
        assert result.index("DWS_A") < result.index("IDX")

    def test_cross_layer_multiple_tasks_per_layer(self):
        """每层多个任务时，所有低层任务排在高层任务之前。"""
        reg = _FakeRegistry(
            deps={"ODS_A": [], "ODS_B": [], "DWD_X": [], "DWD_Y": []},
            layers={"ODS_A": "ODS", "ODS_B": "ODS", "DWD_X": "DWD", "DWD_Y": "DWD"},
        )
        result = topological_sort(["DWD_Y", "ODS_A", "DWD_X", "ODS_B"], reg)
        # 所有 ODS 在所有 DWD 之前
        for ods in ["ODS_A", "ODS_B"]:
            for dwd in ["DWD_X", "DWD_Y"]:
                assert result.index(ods) < result.index(dwd)

    def test_cross_layer_with_explicit_deps_combined(self):
        """隐含层级依赖 + 显式 depends_on 同时生效。"""
        reg = _FakeRegistry(
            deps={
                "ODS_A": [],
                "DWD_LOAD": [],
                "DWS_MONTHLY": ["DWS_DAILY"],
                "DWS_DAILY": [],
            },
            layers={
                "ODS_A": "ODS",
                "DWD_LOAD": "DWD",
                "DWS_DAILY": "DWS",
                "DWS_MONTHLY": "DWS",
            },
        )
        result = topological_sort(
            ["DWS_MONTHLY", "DWS_DAILY", "DWD_LOAD", "ODS_A"], reg
        )
        # 层级：ODS < DWD < DWS
        assert result.index("ODS_A") < result.index("DWD_LOAD")
        assert result.index("DWD_LOAD") < result.index("DWS_DAILY")
        assert result.index("DWD_LOAD") < result.index("DWS_MONTHLY")
        # 显式依赖：DAILY < MONTHLY
        assert result.index("DWS_DAILY") < result.index("DWS_MONTHLY")

    def test_tasks_without_layer_unaffected(self):
        """无 layer 的任务不受层级排序影响，保持原有依赖关系。"""
        reg = _FakeRegistry(
            deps={"UTIL_A": [], "ODS_X": [], "DWD_L": []},
            layers={"ODS_X": "ODS", "DWD_L": "DWD"},
            # UTIL_A 无 layer
        )
        result = topological_sort(["UTIL_A", "DWD_L", "ODS_X"], reg)
        # ODS 仍在 DWD 之前
        assert result.index("ODS_X") < result.index("DWD_L")
        # UTIL_A 无层级约束，应在结果中
        assert "UTIL_A" in result
