"""Tests for VariantConfig Python bindings."""

from pathlib import Path

from rattler_build import PlatformConfig, VariantConfig


def test_variant_config_creation() -> None:
    """Test creating an empty VariantConfig."""
    config = VariantConfig()
    assert len(config) == 0


def test_variant_config_get_values() -> None:
    """Test getting variant values."""
    config = VariantConfig(variants={"python": ["3.9", "3.10", "3.11"]})

    values = config.get_values("python")
    assert values is not None
    assert len(values) == 3


def test_variant_config_get_nonexistent_key() -> None:
    """Test getting values for a key that doesn't exist."""
    config = VariantConfig()

    values = config.get_values("nonexistent")
    assert values is None


def test_variant_config_to_dict() -> None:
    """Test converting VariantConfig to dictionary."""
    config = VariantConfig({"python": ["3.10", "3.11"], "rust": ["1.70", "1.71"]})

    config_dict = config.to_dict()
    assert isinstance(config_dict, dict)
    assert "python" in config_dict
    assert "rust" in config_dict


def test_variant_config_combinations() -> None:
    """Test generating variant combinations."""
    config = VariantConfig({"python": ["3.9", "3.10"], "numpy": ["1.21", "1.22"]})

    combinations = config.combinations()

    # Should have 2 * 2 = 4 combinations
    assert len(combinations) == 4

    # Each combination should be a dict
    for combo in combinations:
        assert isinstance(combo, dict)
        assert "python" in combo
        assert "numpy" in combo


def test_variant_config_from_yaml() -> None:
    """Test loading VariantConfig from YAML string."""
    yaml_content = """
python:
  - "3.9"
  - "3.10"
  - "3.11"
numpy:
  - "1.21"
  - "1.22"
"""

    config = VariantConfig.from_yaml(yaml_content)

    keys = config.keys()
    assert "python" in keys
    assert "numpy" in keys

    python_values = config.get_values("python")
    assert python_values is not None
    assert len(python_values) == 3


def test_variant_config_from_file(tmp_path: Path) -> None:
    """Test loading VariantConfig from a file."""
    yaml_content = """
python:
  - "3.10"
  - "3.11"
rust:
  - "1.70"
"""

    temp_path = tmp_path / "variant.yaml"
    temp_path.write_text(yaml_content)

    config = VariantConfig.from_file(temp_path)

    keys = config.keys()
    assert "python" in keys
    assert "rust" in keys


def test_variant_config_with_different_types() -> None:
    """Test setting variant values with different types."""
    # Strings
    config = VariantConfig({"version": ["1.0", "2.0"]})

    # The values should be stored
    values = config.get_values("version")
    assert values is not None
    assert len(values) == 2


def test_variant_config_len() -> None:
    """Test the __len__ method."""
    config = VariantConfig()
    assert len(config) == 0

    config_one = VariantConfig({"python": ["3.9"]})
    assert len(config_one) == 1

    config_two = VariantConfig({"python": ["3.9"], "numpy": ["1.21"]})
    assert len(config_two) == 2


def test_variant_config_repr() -> None:
    """Test the string representation."""
    config = VariantConfig({"python": ["3.10"]})

    repr_str = repr(config)
    assert "VariantConfig" in repr_str
    assert "keys=1" in repr_str


def test_variant_config_empty_combinations() -> None:
    """Test combinations on empty config."""
    config = VariantConfig()

    combinations = config.combinations()

    # Empty config should give one empty combination
    assert len(combinations) >= 0


def test_variant_config_zip_keys() -> None:
    """Test zip_keys functionality."""
    config_without_zip = VariantConfig({"python": ["3.9", "3.10", "3.11"], "numpy": ["1.20", "1.21", "1.22"]})

    # Initially, zip_keys should be None
    assert config_without_zip.zip_keys is None

    # Without zip_keys, we get all combinations (3 * 3 = 9)
    combinations = config_without_zip.combinations()
    assert len(combinations) == 9

    # Create new config with zip_keys to synchronize python and numpy
    config = VariantConfig(
        {"python": ["3.9", "3.10", "3.11"], "numpy": ["1.20", "1.21", "1.22"]}, zip_keys=[["python", "numpy"]]
    )
    assert config.zip_keys == [["python", "numpy"]]

    # With zip_keys, we get only synchronized combinations (3)
    combinations = config.combinations()
    assert len(combinations) == 3

    # Verify the combinations are synchronized
    for i, combo in enumerate(combinations):
        assert combo["python"] == ["3.9", "3.10", "3.11"][i]
        assert combo["numpy"] == ["1.20", "1.21", "1.22"][i]


def test_variant_config_from_yaml_with_zip_keys() -> None:
    """Test loading VariantConfig from YAML with zip_keys."""
    yaml_content = """
python:
  - "3.9"
  - "3.10"
numpy:
  - "1.20"
  - "1.21"
zip_keys:
  - [python, numpy]
"""

    config = VariantConfig.from_yaml(yaml_content)

    # Check that zip_keys were parsed correctly
    assert config.zip_keys is not None
    assert len(config.zip_keys) == 1
    assert config.zip_keys[0] == ["python", "numpy"]

    # Check that combinations are synchronized
    combinations = config.combinations()
    assert len(combinations) == 2  # Not 4


def test_variant_config_from_file_with_context(tmp_path: Path) -> None:
    """Test loading VariantConfig with JinjaConfig context."""
    from rattler_build import JinjaConfig

    yaml_content = """
c_compiler:
  - if: unix
    then: gcc
  - if: win
    then: msvc
"""

    temp_path = tmp_path / "variant.yaml"
    temp_path.write_text(yaml_content)

    # Load with Linux context
    platform_config = PlatformConfig(target_platform="linux-64")

    jinja_config_linux = JinjaConfig(platform=platform_config)
    config_linux = VariantConfig.from_file_with_context(temp_path, jinja_config_linux)

    values_linux = config_linux.get_values("c_compiler")
    assert values_linux is not None
    assert "gcc" in values_linux
    assert "msvc" not in values_linux

    # Load with Windows context
    platform_config = PlatformConfig(target_platform="win-64")

    jinja_config_win = JinjaConfig(platform=platform_config)
    config_win = VariantConfig.from_file_with_context(temp_path, jinja_config_win)

    values_win = config_win.get_values("c_compiler")
    assert values_win is not None
    assert "msvc" in values_win
    assert "gcc" not in values_win


def test_variant_config_from_yaml_with_context() -> None:
    """Test loading VariantConfig from YAML string with JinjaConfig context."""
    from rattler_build import JinjaConfig

    yaml_content = """
c_compiler:
  - if: unix
    then: gcc
  - if: win
    then: msvc
cxx_compiler:
  - if: unix
    then: gxx
  - if: win
    then: msvc
"""

    # Load with Linux context
    platform_config = PlatformConfig(target_platform="linux-64")

    jinja_config_linux = JinjaConfig(platform=platform_config)
    config_linux = VariantConfig.from_yaml_with_context(yaml_content, jinja_config_linux)

    c_values = config_linux.get_values("c_compiler")
    assert c_values is not None
    assert "gcc" in c_values

    cxx_values = config_linux.get_values("cxx_compiler")
    assert cxx_values is not None
    assert "gxx" in cxx_values


def test_variant_config_from_conda_build_config(tmp_path: Path) -> None:
    """Test loading conda_build_config.yaml format with selectors."""
    from rattler_build import JinjaConfig

    yaml_content = """
python:
  - 3.9
  - 3.10  # [unix]
  - 3.11  # [osx]
c_compiler:
  - gcc       # [linux]
  - clang     # [osx]
  - vs2019    # [win]
"""

    temp_path = tmp_path / "variant.yaml"
    temp_path.write_text(yaml_content)

    # Load with Linux context
    platform_config = PlatformConfig(target_platform="linux-64")

    jinja_config_linux = JinjaConfig(platform=platform_config)
    config_linux = VariantConfig.from_conda_build_config(temp_path, jinja_config_linux)

    python_values = config_linux.get_values("python")
    assert python_values is not None
    assert len(python_values) == 2  # 3.9 and 3.10 (unix selector)

    c_compiler_values = config_linux.get_values("c_compiler")
    assert c_compiler_values is not None
    assert "gcc" in c_compiler_values
    assert "clang" not in c_compiler_values
    assert "vs2019" not in c_compiler_values

    # Load with macOS context
    platform_config = PlatformConfig(target_platform="osx-64")

    jinja_config_osx = JinjaConfig(platform=platform_config)
    config_osx = VariantConfig.from_conda_build_config(temp_path, jinja_config_osx)

    python_values_osx = config_osx.get_values("python")
    assert python_values_osx is not None
    assert len(python_values_osx) == 3  # 3.9, 3.10 (unix), and 3.11 (osx)

    c_compiler_values_osx = config_osx.get_values("c_compiler")
    assert c_compiler_values_osx is not None
    assert "clang" in c_compiler_values_osx
    assert "gcc" not in c_compiler_values_osx

    # Load with Windows context
    platform_config = PlatformConfig(target_platform="win-64")

    jinja_config_win = JinjaConfig(platform=platform_config)
    config_win = VariantConfig.from_conda_build_config(temp_path, jinja_config_win)

    python_values_win = config_win.get_values("python")
    assert python_values_win is not None
    assert len(python_values_win) == 1  # Only 3.9 (no unix/osx selectors match)

    c_compiler_values_win = config_win.get_values("c_compiler")
    assert c_compiler_values_win is not None
    assert "vs2019" in c_compiler_values_win


def test_variant_config_from_files(tmp_path: Path) -> None:
    """Test loading and merging multiple variant config files."""
    base_yaml = """
python:
  - "3.9"
  - "3.10"
numpy:
  - "1.21"
"""

    override_yaml = """
python:
  - "3.11"
  - "3.12"
rust:
  - "1.75"
"""

    base_path = tmp_path / "base.yaml"
    base_path.write_text(base_yaml)

    override_path = tmp_path / "override.yaml"
    override_path.write_text(override_yaml)

    config = VariantConfig.from_files([base_path, override_path])

    # python should be overridden by the later file
    python_values = config.get_values("python")
    assert python_values is not None
    assert python_values == ["3.11", "3.12"]

    # numpy from the base file should still be present
    numpy_values = config.get_values("numpy")
    assert numpy_values is not None
    assert numpy_values == ["1.21"]

    # rust from the override file should be present
    rust_values = config.get_values("rust")
    assert rust_values is not None
    assert rust_values == ["1.75"]


def test_variant_config_from_files_single(tmp_path: Path) -> None:
    """Test from_files with a single file behaves like from_file."""
    yaml_content = """
python:
  - "3.10"
"""

    path = tmp_path / "variant.yaml"
    path.write_text(yaml_content)

    config = VariantConfig.from_files([path])

    values = config.get_values("python")
    assert values is not None
    assert values == ["3.10"]


def test_variant_config_from_files_empty() -> None:
    """Test from_files with an empty list produces an empty config."""
    config = VariantConfig.from_files([])
    assert len(config) == 0


def test_variant_config_from_files_zip_keys_precedence(tmp_path: Path) -> None:
    """Test that zip_keys from the last file take precedence."""
    first_yaml = """
python:
  - "3.9"
  - "3.10"
numpy:
  - "1.20"
  - "1.21"
zip_keys:
  - [python, numpy]
"""

    second_yaml = """
rust:
  - "1.70"
  - "1.75"
"""

    first_path = tmp_path / "first.yaml"
    first_path.write_text(first_yaml)
    second_path = tmp_path / "second.yaml"
    second_path.write_text(second_yaml)

    config = VariantConfig.from_files([first_path, second_path])

    # zip_keys from the first file should be preserved (second file has none)
    assert config.zip_keys is not None
    assert config.zip_keys == [["python", "numpy"]]

    # With zip_keys, python+numpy produce 2 combos, crossed with 2 rust = 4
    combinations = config.combinations()
    assert len(combinations) == 4


def test_variant_config_from_files_with_context(tmp_path: Path) -> None:
    """Test loading multiple files with JinjaConfig context."""
    from rattler_build import JinjaConfig

    base_yaml = """
c_compiler:
  - if: unix
    then: gcc
  - if: win
    then: msvc
"""

    override_yaml = """
python:
  - "3.11"
"""

    base_path = tmp_path / "base.yaml"
    base_path.write_text(base_yaml)
    override_path = tmp_path / "override.yaml"
    override_path.write_text(override_yaml)

    platform_config = PlatformConfig(target_platform="linux-64")
    jinja_config = JinjaConfig(platform=platform_config)
    config = VariantConfig.from_files_with_context([base_path, override_path], jinja_config)

    c_values = config.get_values("c_compiler")
    assert c_values is not None
    assert "gcc" in c_values
    assert "msvc" not in c_values

    python_values = config.get_values("python")
    assert python_values is not None
    assert python_values == ["3.11"]


def test_variant_config_merge() -> None:
    """Test merging two VariantConfigs."""
    base = VariantConfig({"python": ["3.9", "3.10"], "numpy": ["1.21"]})
    override = VariantConfig({"python": ["3.11"], "rust": ["1.75"]})

    merged = base.merge(override)

    # python should be overridden
    assert merged["python"] == ["3.11"]
    # numpy should be preserved from base
    assert merged["numpy"] == ["1.21"]
    # rust should be added from override
    assert merged["rust"] == ["1.75"]

    # Original configs should be unchanged
    assert base["python"] == ["3.9", "3.10"]
    assert override.get_values("numpy") is None


def test_variant_config_merge_zip_keys() -> None:
    """Test that merge replaces zip_keys when other has them."""
    base = VariantConfig(
        {"python": ["3.9", "3.10"], "numpy": ["1.20", "1.21"]},
        zip_keys=[["python", "numpy"]],
    )
    override = VariantConfig({"rust": ["1.70"]})

    # Merging with no zip_keys preserves the original
    merged = base.merge(override)
    assert merged.zip_keys == [["python", "numpy"]]

    # Merging with new zip_keys replaces
    override_with_zk = VariantConfig(
        {"c_compiler": ["gcc", "clang"], "cxx_compiler": ["gxx", "clangxx"]},
        zip_keys=[["c_compiler", "cxx_compiler"]],
    )
    merged2 = base.merge(override_with_zk)
    assert merged2.zip_keys == [["c_compiler", "cxx_compiler"]]


def test_variant_config_multiple_zip_key_groups() -> None:
    """Test multiple zip_key groups."""
    config = VariantConfig(
        {
            "python": ["3.9", "3.10"],
            "numpy": ["1.20", "1.21"],
            "c_compiler": ["gcc", "clang"],
            "cxx_compiler": ["gxx", "clangxx"],
        },
        zip_keys=[["python", "numpy"], ["c_compiler", "cxx_compiler"]],
    )

    # Should get 2 * 2 = 4 combinations (not 2 * 2 * 2 * 2 = 16)
    combinations = config.combinations()
    assert len(combinations) == 4

    # Check that synchronization is preserved
    for combo in combinations:
        # python-numpy should be synchronized
        if combo["python"] == "3.9":
            assert combo["numpy"] == "1.20"
        else:
            assert combo["numpy"] == "1.21"

        # c_compiler-cxx_compiler should be synchronized
        if combo["c_compiler"] == "gcc":
            assert combo["cxx_compiler"] == "gxx"
        else:
            assert combo["cxx_compiler"] == "clangxx"
