# -*- coding: utf-8 -*-
"""--layers CLI 参数单元测试 + 属性测试

验证 parse_layers() 解析、--layers 与 --flow/--pipeline 互斥、
以及 --layers 参数在 argparse 中的注册。

需求: 6.1, 6.2, 6.3, 6.4
"""
import pytest
import string
from unittest.mock import patch

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

from cli.main import parse_layers, parse_args, VALID_LAYERS


# ---------------------------------------------------------------------------
# 1. parse_layers() 解析
# ---------------------------------------------------------------------------
class TestParseLayers:
    """parse_layers() 函数测试"""

    def test_single_layer(self):
        assert parse_layers("ODS") == ["ODS"]

    def test_two_layers(self):
        assert parse_layers("ODS,DWD") == ["ODS", "DWD"]

    def test_all_layers(self):
        result = parse_layers("ODS,DWD,DWS,INDEX")
        assert result == ["ODS", "DWD", "DWS", "INDEX"]

    def test_case_insensitive(self):
        """小写输入自动转大写"""
        assert parse_layers("ods,dwd") == ["ODS", "DWD"]

    def test_mixed_case(self):
        assert parse_layers("Ods,dWd,DWS") == ["ODS", "DWD", "DWS"]

    def test_whitespace_trimmed(self):
        """逗号周围的空格被去除"""
        assert parse_layers(" ODS , DWD ") == ["ODS", "DWD"]

    def test_invalid_layer_raises(self):
        with pytest.raises(ValueError, match="无效的层名"):
            parse_layers("ODS,INVALID")

    def test_all_invalid_raises(self):
        with pytest.raises(ValueError, match="无效的层名"):
            parse_layers("FOO,BAR")

    def test_empty_string_raises(self):
        with pytest.raises(ValueError, match="不能为空"):
            parse_layers("")

    def test_only_commas_raises(self):
        with pytest.raises(ValueError, match="不能为空"):
            parse_layers(",,,")

    def test_valid_layers_constant(self):
        """VALID_LAYERS 包含且仅包含四个合法层名"""
        assert VALID_LAYERS == {"ODS", "DWD", "DWS", "INDEX"}


# ---------------------------------------------------------------------------
# 2. --layers argparse 注册
# ---------------------------------------------------------------------------
class TestLayersArgParsing:
    """--layers 参数在 argparse 中的行为"""

    def test_layers_parsed(self):
        with patch("sys.argv", ["cli", "--layers", "ODS,DWD"]):
            args = parse_args()
        assert args.layers == "ODS,DWD"

    def test_layers_default_is_none(self):
        with patch("sys.argv", ["cli"]):
            args = parse_args()
        assert args.layers is None



# ---------------------------------------------------------------------------
# 3. --layers 与 --pipeline 互斥（需求 6.4）
# ---------------------------------------------------------------------------
class TestLayersFlowMutualExclusion:
    """--layers 和 --flow 互斥校验

    互斥校验在 main() 中实现（非 argparse 层），
    此处验证两个参数可以同时被解析（互斥由 main 层处理）。
    """

    def test_both_args_can_be_parsed(self):
        """argparse 层允许同时传入 --layers 和 --flow，互斥由 main() 校验"""
        with patch("sys.argv", [
            "cli", "--layers", "ODS,DWD", "--flow", "api_full",
        ]):
            args = parse_args()
        assert args.layers == "ODS,DWD"
        assert args.flow == "api_full"



# ---------------------------------------------------------------------------
# 4. 属性测试 — Property 5: --layers 解析正确性
# Feature: etl-dws-flow-refactor, Property 5: --layers 解析正确性
# Validates: Requirements 6.1, 6.2
# ---------------------------------------------------------------------------

# 合法层名集合
_ALL_LAYERS = sorted(VALID_LAYERS)  # ["DWD", "DWS", "INDEX", "ODS"]

# 策略：生成 {ODS, DWD, DWS, INDEX} 的非空子集
_layer_subsets = st.lists(
    st.sampled_from(_ALL_LAYERS),
    min_size=1,
    max_size=4,
    unique=True,
)

# 策略：生成不在合法层名中的随机字符串
_invalid_layer = st.text(
    alphabet=string.ascii_letters,
    min_size=1,
    max_size=10,
).filter(lambda s: s.upper() not in VALID_LAYERS)


class TestParseLayersProperties:
    """parse_layers() 属性测试

    # Feature: etl-dws-flow-refactor, Property 5: --layers 解析正确性
    """

    @given(subset=_layer_subsets)
    @settings(max_examples=100)
    def test_valid_subset_roundtrip(self, subset: list[str]):
        """任意合法层子集经逗号拼接后，parse_layers 返回恰好该子集（大写）。

        **Validates: Requirements 6.1, 6.2**
        """
        raw = ",".join(subset)
        result = parse_layers(raw)
        assert set(result) == set(subset)
        # 所有元素均为大写
        assert all(l == l.upper() for l in result)
        # 元素数量与输入一致
        assert len(result) == len(subset)

    @given(subset=_layer_subsets)
    @settings(max_examples=100)
    def test_case_insensitive_roundtrip(self, subset: list[str]):
        """任意大小写变体经 parse_layers 后均返回大写结果。

        **Validates: Requirements 6.1, 6.2**
        """
        # 随机变换大小写：全小写
        raw = ",".join(l.lower() for l in subset)
        result = parse_layers(raw)
        assert set(result) == set(subset)

    @given(subset=_layer_subsets)
    @settings(max_examples=100)
    def test_whitespace_tolerance(self, subset: list[str]):
        """逗号周围添加空格不影响解析结果。

        **Validates: Requirements 6.1, 6.2**
        """
        raw = " , ".join(f"  {l}  " for l in subset)
        result = parse_layers(raw)
        assert set(result) == set(subset)

    @given(
        valid=_layer_subsets,
        bad=_invalid_layer,
    )
    @settings(max_examples=100)
    def test_invalid_layer_raises_valueerror(self, valid: list[str], bad: str):
        """包含无效层名时必须抛出 ValueError。

        **Validates: Requirements 6.1, 6.2**
        """
        # 确保 bad 确实不在合法层名中（filter 已保证，双重保险）
        assume(bad.upper() not in VALID_LAYERS)
        raw = ",".join(valid + [bad])
        with pytest.raises(ValueError, match="无效的层名"):
            parse_layers(raw)

    @given(bad=_invalid_layer)
    @settings(max_examples=100)
    def test_all_invalid_raises_valueerror(self, bad: str):
        """纯无效层名字符串必须抛出 ValueError。

        **Validates: Requirements 6.1, 6.2**
        """
        assume(bad.upper() not in VALID_LAYERS)
        with pytest.raises(ValueError, match="无效的层名"):
            parse_layers(bad)


# ---------------------------------------------------------------------------
# 5. --flow / --pipeline 弃用别名测试（需求 9.3, 9.4）
# ---------------------------------------------------------------------------
class TestFlowParameter:
    """--flow 参数测试（--pipeline 已移除）"""

    def test_flow_parsed(self):
        """--flow 作为主参数可正常解析"""
        with patch("sys.argv", ["cli", "--flow", "api_full"]):
            args = parse_args()
        assert args.flow == "api_full"

    def test_flow_default_is_none(self):
        """未指定 --flow 时默认为 None"""
        with patch("sys.argv", ["cli"]):
            args = parse_args()
        assert args.flow is None

    def test_layers_and_flow_mutually_exclusive(self):
        """--layers 和 --flow 互斥（argparse 层可同时解析，main() 校验）"""
        with patch("sys.argv", ["cli", "--layers", "ODS,DWD", "--flow", "api_full"]):
            args = parse_args()
        assert args.layers == "ODS,DWD"
        assert args.flow == "api_full"

