"""Unit tests for boolean cleaning variant (Clean.boolean)."""

import polars as pl
import pytest
import zeolite as z
from zeolite.exceptions import CleanConfigurationError


class TestCleanBoolean:
    """Tests for Clean.boolean with true/false value mapping."""

    def test_clean_boolean_basic(self):
        """Test basic boolean cleaning instantiation and apply."""
        ref = z.ref("value")
        clean = z.Clean.boolean()
        expr = clean.apply(ref)

        df = pl.DataFrame({"value": ["yes", "no", "true"]})
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        assert result.to_list() == [True, False, True]
        assert result.dtype == pl.Boolean

    def test_clean_boolean_handles_all_cases(self):
        """Test boolean cleaning with various true/false values, nulls, and invalid values."""
        ref = z.ref("value")
        clean = z.Clean.boolean(
            true_values={"yes", "y", "true", "1"},
            false_values={"no", "n", "false", "0"},
        )
        expr = clean.apply(ref)

        df = pl.DataFrame(
            {
                "value": [
                    "yes",  # True
                    "Y",  # True (sanitised)
                    " TRUE ",  # True (sanitised)
                    "1",  # True
                    "no",  # False
                    "N",  # False (sanitised)
                    " false ",  # False (sanitised)
                    "0",  # False
                    None,  # Null
                    "maybe",  # Invalid
                ]
            }
        )
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        assert result.to_list() == [
            True,
            True,
            True,
            True,
            False,
            False,
            False,
            False,
            None,
            None,
        ]

    def test_clean_boolean_requires_sets(self):
        """Test that true_values and false_values must be sets."""
        with pytest.raises(CleanConfigurationError) as exc_info:
            z.Clean.boolean(true_values=["yes", "true"])

        assert "true_values must be a set or frozenset" in str(exc_info.value)

    def test_clean_boolean_disjoint_sets_required(self):
        """Test that true_values and false_values must be disjoint."""
        with pytest.raises(CleanConfigurationError) as exc_info:
            z.Clean.boolean(true_values={"yes", "maybe"}, false_values={"no", "maybe"})

        assert "must be disjoint sets" in str(exc_info.value)

    def test_clean_boolean_whitespace_variations(self, whitespace_df):
        """Test boolean cleaning with various whitespace edge cases."""
        ref = z.ref("value")
        clean = z.Clean.boolean(
            true_values={"yes", "true", "1"}, false_values={"no", "false", "0"}
        )
        expr = clean.apply(ref)

        result = whitespace_df.with_columns(expr).select(ref.clean().col).to_series()

        # Empty strings and whitespace should become null
        assert result.item(0) is None  # ""
        assert result.item(1) is None  # "   "
        assert result.item(2) is None  # "\t"
        assert result.item(3) is None  # "\n"
        assert result.item(4) is None  # "\t\n"
        # Test values with whitespace should be sanitised and processed
        assert result.item(5) is None  # "  test  " - not in true/false values
        assert result.item(6) is None  # "\ttest\t" - not in true/false values
        assert result.item(7) is None  # "\ntest\n" - not in true/false values

    def test_clean_boolean_case_edge_cases(self):
        """Test boolean cleaning with various case combinations."""
        ref = z.ref("value")
        clean = z.Clean.boolean(
            true_values={"yes", "true", "1"}, false_values={"no", "false", "0"}
        )
        expr = clean.apply(ref)

        df = pl.DataFrame(
            {"value": ["TRUE", "False", "YES", "No", "True", "FALSE", "1", "0"]}
        )
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        # All case variations should be handled by sanitisation
        assert result.to_list() == [True, False, True, False, True, False, True, False]

    def test_clean_boolean_empty_sets_error(self):
        """Test that empty true/false values sets raise an error."""
        with pytest.raises(CleanConfigurationError) as exc_info:
            z.Clean.boolean(true_values=set(), false_values=set())

        assert "true_values and/or false_values must be non-empty" in str(
            exc_info.value
        )

    @pytest.mark.parametrize("input_val", ["null", "NULL", "None", "none"])
    def test_clean_boolean_null_string_variations(self, input_val):
        """Test boolean cleaning with various null string representations."""
        ref = z.ref("value")
        clean = z.Clean.boolean()
        expr = clean.apply(ref)

        df = pl.DataFrame({"value": [input_val]})
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        # Null-like strings should become null
        assert result.item(0) is None
