# -*- coding: utf-8 -*-
"""编排层属性测试 — 验证 FlowRunner、TaskExecutor、CLI 的核心正确性属性。

Feature: etl-flow-debug
使用 hypothesis 对 Flow 层解析、无效 Flow 拒绝、工具类任务跳过游标、
CLI data_source 解析进行属性测试。
"""
from __future__ import annotations

import os
import sys
import types
import logging
from pathlib import Path
from unittest.mock import MagicMock

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

PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

os.environ.setdefault("ETL_SKIP_DOTENV", "1")

from orchestration.flow_runner import FlowRunner
from orchestration.task_registry import default_registry, TaskRegistry


# ---------------------------------------------------------------------------
# 公共常量
# ---------------------------------------------------------------------------

VALID_FLOWS = list(FlowRunner.FLOW_LAYERS.keys())

# 从 default_registry 中收集所有 requires_db_config=False 的任务代码（即工具类任务）
UTILITY_TASK_CODES = [
    code for code in default_registry.get_all_task_codes()
    if default_registry.is_utility_task(code)
]

# ---------------------------------------------------------------------------
# Property 9: FlowRunner Flow 层解析
# ---------------------------------------------------------------------------


@settings(max_examples=100, suppress_health_check=[HealthCheck.too_slow])
@given(flow_name=st.sampled_from(VALID_FLOWS))
def test_property9_pipeline_runner_flow_layer_resolution(flow_name: str):
    """Property 9: FlowRunner Flow 层解析

    对任意有效的 Flow 名称（FLOW_LAYERS 的键），FlowRunner 解析出的
    层列表应与 FLOW_LAYERS 中定义的值完全一致。

    **Validates: Requirements 5.1, 10.2**
    """
    expected_layers = FlowRunner.FLOW_LAYERS[flow_name]

    # 直接验证 FLOW_LAYERS 字典查找——这是 FlowRunner.run() 内部
    # 解析层列表的唯一路径：`layers = self.FLOW_LAYERS[flow]`
    assert flow_name in FlowRunner.FLOW_LAYERS
    assert FlowRunner.FLOW_LAYERS[flow_name] == expected_layers

    # 验证层列表非空且元素为字符串
    assert len(expected_layers) > 0, f"Flow {flow_name} 的层列表不应为空"
    for layer in expected_layers:
        assert isinstance(layer, str)
        assert layer in ("ODS", "DWD", "DWS", "INDEX"), (
            f"Flow {flow_name} 包含未知层: {layer}"
        )


# ---------------------------------------------------------------------------
# Property 10: FlowRunner 无效 Flow 拒绝
# ---------------------------------------------------------------------------

# 生成不在 FLOW_LAYERS 键集合中的字符串
_invalid_flow_strategy = st.text(
    alphabet=st.characters(whitelist_categories=("L", "N", "P")),
    min_size=1,
    max_size=50,
).filter(lambda s: s not in FlowRunner.FLOW_LAYERS)


@settings(max_examples=100, suppress_health_check=[HealthCheck.too_slow])
@given(invalid_flow=_invalid_flow_strategy)
def test_property10_pipeline_runner_rejects_invalid_flow(invalid_flow: str):
    """Property 10: FlowRunner 无效 Flow 拒绝

    对任意不在 FLOW_LAYERS 键集合中的字符串，FlowRunner.run()
    应抛出 ValueError。

    **Validates: Requirements 5.2**

    实现方式：直接复现 FlowRunner.run() 中的校验逻辑，
    避免构造完整的 FlowRunner 实例（需要真实 DB/API 连接）。
    """
    # FlowRunner.run() 的第一行就是：
    #   if flow not in self.FLOW_LAYERS: raise ValueError(...)
    # 我们直接验证这个守卫条件
    assert invalid_flow not in FlowRunner.FLOW_LAYERS

    # 构造一个最小化的 FlowRunner 实例来验证 run() 抛出 ValueError
    mock_config = MagicMock()
    mock_config.get = MagicMock(return_value="Asia/Shanghai")
    mock_config.__getitem__ = MagicMock(return_value={})

    runner = FlowRunner(
        config=mock_config,
        task_executor=MagicMock(),
        task_registry=MagicMock(),
        db_conn=MagicMock(),
        api_client=MagicMock(),
        logger=logging.getLogger("test"),
    )

    with pytest.raises(ValueError, match="无效的 Flow 名称"):
        runner.run(flow=invalid_flow)


# ---------------------------------------------------------------------------
# Property 11: TaskExecutor 工具类任务跳过游标
# ---------------------------------------------------------------------------


@settings(max_examples=100, suppress_health_check=[HealthCheck.too_slow])
@given(task_code=st.sampled_from(UTILITY_TASK_CODES))
def test_property11_task_executor_utility_task_skips_cursor(task_code: str):
    """Property 11: TaskExecutor 工具类任务跳过游标

    对任意被 TaskRegistry 标记为 requires_db_config=False 的任务代码，
    TaskExecutor 应通过 _run_utility_task 路径执行，不调用 CursorManager
    和 RunTracker。

    **Validates: Requirements 5.5**

    验证方式：
    1. 确认 task_code 在 registry 中被标记为工具类任务
    2. 构造 TaskExecutor 实例，用 Mock 替代 CursorManager 和 RunTracker
    3. 调用 run_single_task，验证 CursorManager 和 RunTracker 未被调用
    """
    from orchestration.task_executor import TaskExecutor

    # 1) 确认 registry 元数据
    assert default_registry.is_utility_task(task_code), (
        f"{task_code} 应被标记为工具类任务"
    )
    meta = default_registry.get_metadata(task_code)
    assert meta is not None
    assert meta.requires_db_config is False

    # 2) 构造最小化 TaskExecutor
    mock_config = MagicMock()
    mock_config.get = MagicMock(return_value="Asia/Shanghai")
    mock_config.__getitem__ = MagicMock(return_value={
        "export_root": "/tmp/test_export",
        "log_root": "/tmp/test_log",
        "fetch_root": "/tmp/test_fetch",
    })

    mock_cursor_mgr = MagicMock(spec=["get_or_create", "advance"])
    mock_run_tracker = MagicMock(spec=["create_run", "update_run"])

    # 创建一个假的任务实例，execute 返回成功
    mock_task_instance = MagicMock()
    mock_task_instance.execute = MagicMock(return_value={
        "status": "SUCCESS",
        "counts": {"processed": 1},
    })

    # 用真实的 default_registry，但替换 create_task 以避免真实实例化
    mock_registry = MagicMock(wraps=default_registry)
    mock_registry.is_utility_task = default_registry.is_utility_task
    mock_registry.get_metadata = default_registry.get_metadata
    mock_registry.create_task = MagicMock(return_value=mock_task_instance)

    executor = TaskExecutor(
        config=mock_config,
        db_ops=MagicMock(),
        api_client=MagicMock(),
        cursor_mgr=mock_cursor_mgr,
        run_tracker=mock_run_tracker,
        task_registry=mock_registry,
        logger=logging.getLogger("test"),
    )

    # 3) 执行任务
    result = executor.run_single_task(
        task_code=task_code,
        run_uuid="test-uuid-001",
        store_id=1,
    )

    # 4) 验证：CursorManager 和 RunTracker 不应被调用
    mock_cursor_mgr.get_or_create.assert_not_called()
    mock_cursor_mgr.advance.assert_not_called()
    mock_run_tracker.create_run.assert_not_called()
    mock_run_tracker.update_run.assert_not_called()

    # 验证结果状态
    assert result["status"] == "SUCCESS"


# ---------------------------------------------------------------------------
# Property 12: CLI data_source 解析
# ---------------------------------------------------------------------------

# resolve_data_source 需要一个类似 argparse.Namespace 的对象
_DATA_SOURCE_VALUES = ["online", "offline", "hybrid"]
_PIPELINE_FLOW_VALUES = ["FULL", "FETCH_ONLY", "INGEST_ONLY"]
_FLOW_TO_DATA_SOURCE = {
    "FULL": "hybrid",
    "FETCH_ONLY": "online",
    "INGEST_ONLY": "offline",
}


@settings(max_examples=100, suppress_health_check=[HealthCheck.too_slow])
@given(
    data_source=st.one_of(st.none(), st.sampled_from(_DATA_SOURCE_VALUES)),
    pipeline_flow=st.one_of(st.none(), st.sampled_from(_PIPELINE_FLOW_VALUES)),
)
def test_property12_cli_data_source_resolution(data_source, pipeline_flow):
    """Property 12: CLI data_source 解析

    对任意 --data-source 参数值（online/offline/hybrid）和
    --pipeline-flow 参数值（FULL/FETCH_ONLY/INGEST_ONLY），
    resolve_data_source 应返回正确的映射值，且 --data-source 优先于
    --pipeline-flow。

    **Validates: Requirements 6.3, 6.4**
    """
    from cli.main import resolve_data_source

    # 构造类似 argparse.Namespace 的对象
    args = types.SimpleNamespace(
        data_source=data_source,
        pipeline_flow=pipeline_flow,
    )

    result = resolve_data_source(args)

    # 规则 1：--data-source 优先
    if data_source is not None:
        assert result == data_source, (
            f"当 --data-source={data_source} 时，结果应为 {data_source}，"
            f"实际为 {result}"
        )
    # 规则 2：无 --data-source 时，使用 --pipeline-flow 映射
    elif pipeline_flow is not None:
        expected = _FLOW_TO_DATA_SOURCE[pipeline_flow]
        assert result == expected, (
            f"当 --pipeline-flow={pipeline_flow} 时，结果应为 {expected}，"
            f"实际为 {result}"
        )
    # 规则 3：两者都未指定时，默认 hybrid
    else:
        assert result == "hybrid", (
            f"两者都未指定时，结果应为 hybrid，实际为 {result}"
        )

    # 通用断言：返回值必须是有效的 data_source 值
    assert result in _DATA_SOURCE_VALUES, (
        f"返回值 {result} 不在有效值列表 {_DATA_SOURCE_VALUES} 中"
    )
