# -*- coding: utf-8 -*-
"""
DwsMaintenanceTask — 属性测试 + 单元测试

Feature: etl-dws-flow-refactor, Property 4: DwsMaintenanceTask 配置控制
测试位置：apps/etl/connectors/feiqiu/tests/unit/

属性测试：验证任意 mv_enabled / retention_enabled 布尔组合下，
load() 的行为和返回值结构始终正确。

单元测试：执行顺序（先刷新后清理）、TaskRegistry 注册项替换。
"""
from __future__ import annotations

import sys
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, call, patch

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

# ── 将 ETL 模块加入 sys.path ──
_ETL_ROOT = Path(__file__).resolve().parent.parent.parent
if str(_ETL_ROOT) not in sys.path:
    sys.path.insert(0, str(_ETL_ROOT))

from tasks.base_task import TaskContext
from tasks.dws.maintenance_task import DwsMaintenanceTask


# ============================================================================
# 辅助工具
# ============================================================================

def _build_context(store_id: int = 1) -> TaskContext:
    """构造最小合法 TaskContext。"""
    from zoneinfo import ZoneInfo
    tz = ZoneInfo("Asia/Shanghai")
    return TaskContext(
        store_id=store_id,
        window_start=datetime(2025, 1, 1, tzinfo=tz),
        window_end=datetime(2025, 1, 2, tzinfo=tz),
        window_minutes=1440,
    )


def _make_task(mv_enabled: bool = False, retention_enabled: bool = False) -> DwsMaintenanceTask:
    """构造 DwsMaintenanceTask 实例，通过 config mock 控制开关。"""
    config_map = {
        "dws.mv.enabled": mv_enabled,
        "dws.retention.enabled": retention_enabled,
        "app.timezone": "Asia/Shanghai",
    }
    mock_config = MagicMock()
    mock_config.get.side_effect = lambda key, default=None: config_map.get(key, default)

    mock_db = MagicMock()
    mock_api = MagicMock()
    mock_logger = MagicMock()

    task = DwsMaintenanceTask(mock_config, mock_db, mock_api, mock_logger)
    return task


# ============================================================================
# Property 4: DwsMaintenanceTask 配置控制
# Feature: etl-dws-flow-refactor, Property 4: DwsMaintenanceTask 配置控制
# **Validates: Requirements 4.3, 4.4**
# ============================================================================

class TestMaintenanceConfigProperty:
    """属性测试：任意 mv_enabled / retention_enabled 布尔组合下，
    load() 返回值始终包含 refreshed 和 cleaned 键，
    且仅在对应开关启用时执行对应操作。
    """

    @given(
        mv_enabled=st.booleans(),
        retention_enabled=st.booleans(),
    )
    @settings(max_examples=100)
    def test_load_always_returns_refreshed_and_cleaned(
        self, mv_enabled: bool, retention_enabled: bool
    ):
        """返回的统计字典始终包含 refreshed 和 cleaned 键。"""
        task = _make_task(mv_enabled=mv_enabled, retention_enabled=retention_enabled)
        context = _build_context()

        # mock 内部方法，避免真实 DB 调用
        task._refresh_all_views = MagicMock(return_value=3)
        task._cleanup_all_tables = MagicMock(return_value=42)

        result = task.load({"site_id": 1}, context)

        # 始终包含 counts 键
        assert "counts" in result
        counts = result["counts"]
        # 始终包含 refreshed 和 cleaned
        assert "refreshed" in counts
        assert "cleaned" in counts

    @given(
        mv_enabled=st.booleans(),
        retention_enabled=st.booleans(),
    )
    @settings(max_examples=100)
    def test_mv_refresh_only_when_enabled(
        self, mv_enabled: bool, retention_enabled: bool
    ):
        """仅当 mv_enabled=True 时执行物化视图刷新。"""
        task = _make_task(mv_enabled=mv_enabled, retention_enabled=retention_enabled)
        context = _build_context()

        task._refresh_all_views = MagicMock(return_value=5)
        task._cleanup_all_tables = MagicMock(return_value=10)

        result = task.load({"site_id": 1}, context)
        counts = result["counts"]

        if mv_enabled:
            task._refresh_all_views.assert_called_once()
            assert counts["refreshed"] == 5
        else:
            task._refresh_all_views.assert_not_called()
            assert counts["refreshed"] == 0

    @given(
        mv_enabled=st.booleans(),
        retention_enabled=st.booleans(),
    )
    @settings(max_examples=100)
    def test_retention_cleanup_only_when_enabled(
        self, mv_enabled: bool, retention_enabled: bool
    ):
        """仅当 retention_enabled=True 时执行数据清理。"""
        task = _make_task(mv_enabled=mv_enabled, retention_enabled=retention_enabled)
        context = _build_context()

        task._refresh_all_views = MagicMock(return_value=2)
        task._cleanup_all_tables = MagicMock(return_value=99)

        result = task.load({"site_id": 1}, context)
        counts = result["counts"]

        if retention_enabled:
            task._cleanup_all_tables.assert_called_once_with(context)
            assert counts["cleaned"] == 99
        else:
            task._cleanup_all_tables.assert_not_called()
            assert counts["cleaned"] == 0

    @given(
        mv_enabled=st.booleans(),
        retention_enabled=st.booleans(),
    )
    @settings(max_examples=100)
    def test_counts_are_non_negative_integers(
        self, mv_enabled: bool, retention_enabled: bool
    ):
        """refreshed 和 cleaned 值始终为非负整数。"""
        task = _make_task(mv_enabled=mv_enabled, retention_enabled=retention_enabled)
        context = _build_context()

        task._refresh_all_views = MagicMock(return_value=7)
        task._cleanup_all_tables = MagicMock(return_value=33)

        result = task.load({"site_id": 1}, context)
        counts = result["counts"]

        assert isinstance(counts["refreshed"], int)
        assert isinstance(counts["cleaned"], int)
        assert counts["refreshed"] >= 0
        assert counts["cleaned"] >= 0


# ============================================================================
# 单元测试：执行顺序（先刷新后清理）
# ============================================================================

class TestMaintenanceExecutionOrder:
    """验证 load() 内部先执行 MV 刷新，再执行数据清理。"""

    def test_refresh_before_cleanup(self):
        """当两个开关都启用时，MV 刷新在数据清理之前执行。"""
        task = _make_task(mv_enabled=True, retention_enabled=True)
        context = _build_context()

        call_order = []

        def mock_refresh():
            call_order.append("refresh")
            return 2

        def mock_cleanup(ctx):
            call_order.append("cleanup")
            return 10

        task._refresh_all_views = mock_refresh
        task._cleanup_all_tables = mock_cleanup

        result = task.load({"site_id": 1}, context)

        assert call_order == ["refresh", "cleanup"], (
            f"期望先 refresh 后 cleanup，实际顺序: {call_order}"
        )
        assert result["counts"]["refreshed"] == 2
        assert result["counts"]["cleaned"] == 10

    def test_only_refresh_when_retention_disabled(self):
        """仅启用 MV 刷新时，不执行清理。"""
        task = _make_task(mv_enabled=True, retention_enabled=False)
        context = _build_context()

        task._refresh_all_views = MagicMock(return_value=4)
        task._cleanup_all_tables = MagicMock(return_value=0)

        result = task.load({"site_id": 1}, context)

        task._refresh_all_views.assert_called_once()
        task._cleanup_all_tables.assert_not_called()
        assert result["counts"] == {"refreshed": 4, "cleaned": 0}

    def test_only_cleanup_when_mv_disabled(self):
        """仅启用数据清理时，不执行 MV 刷新。"""
        task = _make_task(mv_enabled=False, retention_enabled=True)
        context = _build_context()

        task._refresh_all_views = MagicMock(return_value=0)
        task._cleanup_all_tables = MagicMock(return_value=50)

        result = task.load({"site_id": 1}, context)

        task._refresh_all_views.assert_not_called()
        task._cleanup_all_tables.assert_called_once()
        assert result["counts"] == {"refreshed": 0, "cleaned": 50}

    def test_both_disabled_returns_zeros(self):
        """两个开关都禁用时，返回全零统计。"""
        task = _make_task(mv_enabled=False, retention_enabled=False)
        context = _build_context()

        task._refresh_all_views = MagicMock()
        task._cleanup_all_tables = MagicMock()

        result = task.load({"site_id": 1}, context)

        task._refresh_all_views.assert_not_called()
        task._cleanup_all_tables.assert_not_called()
        assert result["counts"] == {"refreshed": 0, "cleaned": 0}


# ============================================================================
# 单元测试：TaskRegistry 注册项替换
# ============================================================================

class TestRegistryReplacement:
    """验证 TaskRegistry 中旧的 3 个任务已移除，DWS_MAINTENANCE 已注册。"""

    def test_dws_maintenance_registered(self):
        """DWS_MAINTENANCE 已注册到 default_registry。"""
        from orchestration.task_registry import default_registry
        meta = default_registry.get_metadata("DWS_MAINTENANCE")
        assert meta is not None, "DWS_MAINTENANCE 未在 default_registry 中注册"
        assert meta.task_class is DwsMaintenanceTask
        assert meta.layer == "DWS"

    def test_old_mv_refresh_tasks_removed(self):
        """原 DWS_MV_REFRESH_FINANCE_DAILY 和 DWS_MV_REFRESH_ASSISTANT_DAILY 已移除。"""
        from orchestration.task_registry import default_registry
        all_codes = default_registry.get_all_task_codes()
        assert "DWS_MV_REFRESH_FINANCE_DAILY" not in all_codes, (
            "DWS_MV_REFRESH_FINANCE_DAILY 应已被移除"
        )
        assert "DWS_MV_REFRESH_ASSISTANT_DAILY" not in all_codes, (
            "DWS_MV_REFRESH_ASSISTANT_DAILY 应已被移除"
        )

    def test_old_retention_cleanup_removed(self):
        """原 DWS_RETENTION_CLEANUP 已移除。"""
        from orchestration.task_registry import default_registry
        all_codes = default_registry.get_all_task_codes()
        assert "DWS_RETENTION_CLEANUP" not in all_codes, (
            "DWS_RETENTION_CLEANUP 应已被移除"
        )

    def test_maintenance_in_dws_layer(self):
        """DWS_MAINTENANCE 出现在 DWS 层任务列表中。"""
        from orchestration.task_registry import default_registry
        dws_tasks = default_registry.get_tasks_by_layer("DWS")
        assert "DWS_MAINTENANCE" in dws_tasks


# ============================================================================
# 单元测试：基本属性
# ============================================================================

class TestMaintenanceBasicAttributes:
    """验证 DwsMaintenanceTask 的基本属性。"""

    def test_task_code(self):
        task = _make_task()
        assert task.get_task_code() == "DWS_MAINTENANCE"

    def test_target_table(self):
        task = _make_task()
        assert task.get_target_table() == "dws_maintenance"

    def test_extract_returns_site_id(self):
        """extract() 返回包含 site_id 的字典。"""
        task = _make_task()
        context = _build_context(store_id=42)
        result = task.extract(context)
        assert result == {"site_id": 42}

    def test_transform_passthrough(self):
        """transform() 直接透传输入。"""
        task = _make_task()
        context = _build_context()
        data = {"site_id": 1, "extra": "value"}
        result = task.transform(data, context)
        assert result == data
