"""Unit tests for ID cleaning variant (Clean.id)."""

import polars as pl
import zeolite as z


class TestCleanId:
    """Tests for Clean.id with prefix and sanitisation."""

    def test_clean_id_basic_with_prefix(self):
        """Test basic ID cleaning instantiation and apply with prefix."""
        ref = z.ref("value")
        clean = z.Clean.id(prefix="ORG")
        expr = clean.apply(ref)

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

        assert result.to_list() == ["ORG::123", "ORG::456", "ORG::789"]

    def test_clean_id_handles_all_cases(self):
        """Test ID cleaning with nulls, existing prefixes, whitespace, and sanitisation."""
        ref = z.ref("value")
        clean = z.Clean.id(prefix="ORG", sanitise="full")
        expr = clean.apply(ref)

        df = pl.DataFrame(
            {
                "value": [
                    "123",  # Simple ID
                    " 456 ",  # Whitespace
                    "ORG::789",  # Already has prefix (deduplicated)
                    "ORG::ORG::999",  # Double prefix - sanitise applies to part after stripping first prefix
                    "Test-ID",  # Special chars (sanitised)
                    None,  # Null
                ]
            }
        )
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        assert result.to_list() == [
            "ORG::123",
            "ORG::456",
            "ORG::789",
            "ORG::org_999",  # Double prefix gets sanitised
            "ORG::test_id",
            None,
        ]

    def test_clean_id_custom_separator(self):
        """Test ID cleaning with custom separator."""
        ref = z.ref("value")
        clean = z.Clean.id(prefix="ORG", separator="_")
        expr = clean.apply(ref)

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

        assert result.to_list() == ["ORG_123", "ORG_456"]

    def test_clean_id_without_prefix(self):
        """Test ID cleaning without prefix just applies sanitisation."""
        ref = z.ref("value")
        clean = z.Clean.id(sanitise="full")
        expr = clean.apply(ref)

        df = pl.DataFrame({"value": ["Test-ID", " 123 "]})
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        assert result.to_list() == ["test_id", "123"]

    def test_clean_id_empty_string(self):
        """Test ID cleaning with empty strings."""
        ref = z.ref("value")
        clean = z.Clean.id(prefix="ORG")
        expr = clean.apply(ref)

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

        # Empty strings should get prefix, whitespace preserved
        assert result.to_list() == ["ORG::", "ORG::   ", "ORG::123", None]

    def test_clean_id_special_characters(self):
        """Test ID cleaning with special characters."""
        ref = z.ref("value")
        clean = z.Clean.id(prefix="ORG", sanitise="full")
        expr = clean.apply(ref)

        df = pl.DataFrame({"value": ["test@id", "item#123", "user$name", "data%value"]})
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        # Special characters should be replaced with underscores
        assert result.to_list() == [
            "ORG::test_id",
            "ORG::item_123",
            "ORG::user_name",
            "ORG::data_value",
        ]

    def test_clean_id_whitespace_variations(self, whitespace_df):
        """Test ID cleaning with whitespace edge cases."""
        ref = z.ref("value")
        clean = z.Clean.id(prefix="ORG", sanitise="trim")
        expr = clean.apply(ref)

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

        # Empty strings and whitespace should be trimmed
        assert result.item(0) == "ORG::"  # "" -> "" -> "ORG::"
        assert result.item(1) == "ORG::"  # "   " -> "" -> "ORG::"
        assert result.item(2) == "ORG::"  # "\t" -> "" -> "ORG::"
        assert result.item(3) == "ORG::"  # "\n" -> "" -> "ORG::"
        assert result.item(4) == "ORG::"  # "\t\n" -> "" -> "ORG::"
        # Test values with whitespace should be trimmed and processed
        assert result.item(5) == "ORG::test"  # "  test  " -> "test" -> "ORG::test"
        assert result.item(6) == "ORG::test"  # "\ttest\t" -> "test" -> "ORG::test"
        assert result.item(7) == "ORG::test"  # "\ntest\n" -> "test" -> "ORG::test"

    def test_clean_id_null_string_variations(self, edge_case_strings):
        """Test ID cleaning with various null string representations."""
        ref = z.ref("value")
        clean = z.Clean.id(prefix="ORG")
        expr = clean.apply(ref)

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

        # Null-like strings should be treated as regular strings and get prefix
        assert result.to_list() == [
            "ORG::",  # ""
            "ORG::   ",  # "   "
            "ORG::\t",  # "\t"
            "ORG::\n",  # "\n"
            "ORG::\t\n",  # "\t\n"
            "ORG::null",  # "null"
            "ORG::NULL",  # "NULL"
            "ORG::None",  # "None"
            "ORG::none",  # "none"
        ]

    def test_clean_id_mixed_input_types(self, mixed_type_df):
        """Test ID cleaning with mixed input data types."""
        ref = z.ref("mixed_col")
        clean = z.Clean.id(prefix="ORG")
        expr = clean.apply(ref)

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

        # Mixed types should be converted to strings and get prefix
        assert result.to_list() == [
            "ORG::123",
            "ORG::456",
            None,
            "ORG::abc",
            "ORG::7.89",
            "ORG::",
            "ORG::   ",
        ]

    def test_clean_id_no_prefix_with_sanitise(self):
        """Test ID cleaning without prefix but with sanitisation."""
        ref = z.ref("value")
        clean = z.Clean.id(sanitise="full")
        expr = clean.apply(ref)

        df = pl.DataFrame(
            {"value": ["Test-ID", "user@name", "item#123", "  whitespace  "]}
        )
        result = df.with_columns(expr).select(ref.clean().col).to_series()

        # Should apply sanitisation without adding prefix
        assert result.to_list() == ["test_id", "user_name", "item_123", "whitespace"]
