# -*- coding: utf-8 -*-
"""DwdLoadTask.load() 返回值格式验证 — 单元测试。

验证需求 1.1：load() 返回 errors: int, error_details: list[dict]。
使用 FakeDB/FakeAPI，不涉及真实数据库连接。
"""
from __future__ import annotations

import logging
from datetime import datetime, timezone
from types import SimpleNamespace
from unittest.mock import MagicMock, patch

from tasks.dwd.dwd_load_task import DwdLoadTask


def _make_task() -> DwdLoadTask:
    """构造最小可用的 DwdLoadTask 实例。"""
    config = MagicMock()
    config.get = MagicMock(side_effect=lambda key, default=None: default)

    # 构造 FakeConnection，cursor() 接受任意 kwargs
    fake_conn = MagicMock()
    fake_cursor = MagicMock()
    fake_cursor.__enter__ = MagicMock(return_value=fake_cursor)
    fake_cursor.__exit__ = MagicMock(return_value=False)
    fake_conn.cursor.return_value = fake_cursor

    db = MagicMock()
    db.conn = fake_conn
    api = MagicMock()
    logger = logging.getLogger("test_dwd_return_format")
    return DwdLoadTask(config, db, api, logger)


class TestLoadReturnFormat:
    """验证 load() 返回值中 errors 为 int、error_details 为 list[dict]。"""

    def test_no_errors_returns_zero_and_empty_list(self):
        """无错误时：errors=0, error_details=[]。"""
        task = _make_task()
        # 让 TABLE_MAP 为空，load() 直接返回空结果
        with patch.object(DwdLoadTask, "TABLE_MAP", {}):
            ctx = SimpleNamespace(
                window_start=datetime(2025, 1, 1, tzinfo=timezone.utc),
                window_end=datetime(2025, 1, 2, tzinfo=timezone.utc),
            )
            result = task.load({"now": datetime.now(timezone.utc)}, ctx)

        assert isinstance(result["errors"], int), (
            f"errors 应为 int，实际为 {type(result['errors'])}"
        )
        assert result["errors"] == 0
        assert isinstance(result["error_details"], list)
        assert result["error_details"] == []
        assert "tables" in result

    def test_with_errors_returns_count_and_details(self):
        """有错误时：errors=len(error_details), error_details 包含错误字典。"""
        task = _make_task()

        # 模拟一张表装载失败：_get_columns 抛异常
        fake_cursor = task.db.conn.cursor.return_value.__enter__.return_value
        fake_cursor.fetchall.return_value = []

        def fake_get_columns(cur, table):
            if "dim_test" in table:
                raise RuntimeError("模拟装载失败")
            return []

        task._get_columns = fake_get_columns

        with patch.object(
            DwdLoadTask, "TABLE_MAP", {"dwd.dim_test": "ods.test_source"}
        ):
            ctx = SimpleNamespace(
                window_start=datetime(2025, 1, 1, tzinfo=timezone.utc),
                window_end=datetime(2025, 1, 2, tzinfo=timezone.utc),
            )
            result = task.load({"now": datetime.now(timezone.utc)}, ctx)

        assert isinstance(result["errors"], int)
        assert result["errors"] == 1
        assert isinstance(result["error_details"], list)
        assert len(result["error_details"]) == 1
        assert result["error_details"][0]["table"] == "dwd.dim_test"
        assert "模拟装载失败" in result["error_details"][0]["error"]

    def test_errors_equals_len_of_error_details(self):
        """errors 值始终等于 error_details 列表长度。"""
        task = _make_task()

        # 模拟两张表都失败
        def fake_get_columns(cur, table):
            raise RuntimeError(f"失败: {table}")

        task._get_columns = fake_get_columns

        with patch.object(
            DwdLoadTask,
            "TABLE_MAP",
            {"dwd.dim_a": "ods.a", "dwd.dim_b": "ods.b"},
        ):
            ctx = SimpleNamespace(
                window_start=datetime(2025, 1, 1, tzinfo=timezone.utc),
                window_end=datetime(2025, 1, 2, tzinfo=timezone.utc),
            )
            result = task.load({"now": datetime.now(timezone.utc)}, ctx)

        assert result["errors"] == len(result["error_details"])
        assert result["errors"] == 2

    def test_successful_tables_in_summary(self):
        """成功装载的表出现在 tables 列表中，不影响 errors 计数。"""
        task = _make_task()

        # dim_ok 返回空列 → 跳过（不报错也不计入 summary）
        # dim_fail 抛异常 → 计入 errors
        call_count = 0

        def fake_get_columns(cur, table):
            nonlocal call_count
            call_count += 1
            if "fail" in table:
                raise RuntimeError("boom")
            return []  # 空列 → 跳过

        task._get_columns = fake_get_columns

        with patch.object(
            DwdLoadTask,
            "TABLE_MAP",
            {"dwd.dim_ok": "ods.ok", "dwd.dim_fail": "ods.fail"},
        ):
            ctx = SimpleNamespace(
                window_start=datetime(2025, 1, 1, tzinfo=timezone.utc),
                window_end=datetime(2025, 1, 2, tzinfo=timezone.utc),
            )
            result = task.load({"now": datetime.now(timezone.utc)}, ctx)

        assert result["errors"] == 1
        assert len(result["error_details"]) == 1
        # dim_ok 因空列被跳过，不在 summary 中
        assert result["tables"] == []
