"""Tests for TabularValidator edge cases."""

import polars as pl
import pytest

from axm.catalog.entries import Identity, Location, Owner
from axm_tabular.entries.tabular import TabularEntry, TabularSchema, TabularSpec
from axm_tabular.validation.tabular import TabularValidator


class TestTabularValidatorCoverage:
    """Tests to improve coverage of TabularValidator."""

    @pytest.fixture
    def basic_entry(self) -> TabularEntry:
        """Create a basic tabular entry for testing."""
        return TabularEntry(
            name="test_entry",
            version="1.0",
            identity=Identity(
                display_name="Test",
                description="Test",
                domain="test",
                owner=Owner(team="test", contact="test@test.com"),
            ),
            data_schema=TabularSchema(
                spec=TabularSpec(
                    columns=[
                        {"name": "id", "type": "int64", "nullable": False},
                        {"name": "name", "type": "string", "nullable": True},
                    ]
                )
            ),
            location=Location(storage="fs", path_template="/"),
        )

    def test_invalid_input_type(self, basic_entry: TabularEntry) -> None:
        """Should return error if input is not a DataFrame."""
        validator = TabularValidator(basic_entry)
        result = validator.validate({"not": "a dataframe"})
        assert not result.is_valid
        assert "Expected polars DataFrame" in result.errors[0].message

    def test_missing_column(self, basic_entry: TabularEntry) -> None:
        """Should return error if required column is missing."""
        validator = TabularValidator(basic_entry)
        df = pl.DataFrame({"id": [1]})
        result = validator.validate(df)

        assert not result.is_valid
        error_msgs = [e.message for e in result.errors]
        assert "Missing column: name" in error_msgs[0]

    def test_type_mismatch(self, basic_entry: TabularEntry) -> None:
        """Should return error if column type doesn't match."""
        validator = TabularValidator(basic_entry)
        df = pl.DataFrame({"id": ["1"], "name": ["alice"]})
        result = validator.validate(df)

        assert not result.is_valid
        error_msgs = [e.message for e in result.errors]
        assert "Type mismatch for column 'id'" in error_msgs[0]

    def test_non_nullable_constraint(self, basic_entry: TabularEntry) -> None:
        """Should return error if non-nullable column has nulls."""
        validator = TabularValidator(basic_entry)
        df = pl.DataFrame({"id": [1, None], "name": ["alice", "bob"]})
        result = validator.validate(df)

        assert not result.is_valid
        error_msgs = [e.message for e in result.errors]
        assert "Column 'id' is not nullable" in error_msgs[0]

    def test_valid_dataframe_passes(self, basic_entry: TabularEntry) -> None:
        """Valid dataframe should pass validation."""
        validator = TabularValidator(basic_entry)
        df = pl.DataFrame({"id": [1, 2], "name": ["alice", "bob"]})
        result = validator.validate(df)
        assert result.is_valid

    def test_unknown_type_mapping(self) -> None:
        """Should return error (fail check) for unknown type mapping."""
        entry = TabularEntry(
            name="type_test",
            version="1.0",
            identity=Identity(
                display_name="T",
                description="D",
                domain="D",
                owner=Owner(team="T", contact="C"),
            ),
            data_schema=TabularSchema(
                spec=TabularSpec(
                    columns=[
                        # 'complex_type' is not in _check_type mapping
                        {"name": "data", "type": "complex_type"}
                    ]
                )
            ),
            location=Location(storage="fs", path_template="/"),
        )
        validator = TabularValidator(entry)
        # We provide match for 'unknown' just to see it fail type check
        df = pl.DataFrame({"data": ["some string"]})

        result = validator.validate(df)
        assert not result.is_valid
        assert "Type mismatch" in result.errors[0].message

    def test_generic_data_entry_support(self) -> None:
        """Should support generic DataEntry with dict usage for spec."""
        from axm.catalog.entries import DataEntry, Schema, SchemaFormat

        entry = DataEntry(
            name="generic_test",
            version="1.0",
            identity=Identity(
                display_name="T",
                description="D",
                domain="D",
                owner=Owner(team="T", contact="C"),
            ),
            data_schema=Schema(
                format=SchemaFormat.TABULAR,
                spec={"columns": [{"name": "id", "type": "int64"}]},
            ),
            location=Location(storage="fs", path_template="/"),
        )
        validator = TabularValidator(entry)
        df = pl.DataFrame({"id": [1]})
        result = validator.validate(df)
        assert result.is_valid


class TestTabularValidatorRuleEnforcement:
    """Tests for column rule enforcement via RuleEngine integration."""

    def test_pattern_rule_rejects_invalid_data(self) -> None:
        """Column with pattern rule should reject non-matching values."""
        entry = TabularEntry(
            name="pattern_test",
            version="1.0",
            identity=Identity(
                display_name="Pattern Test",
                description="Test pattern rule",
                domain="test",
                owner=Owner(team="test", contact="test@test.com"),
            ),
            data_schema=TabularSchema(
                spec=TabularSpec(
                    columns=[
                        {
                            "name": "code",
                            "type": "string",
                            "rules": [
                                {
                                    "rule": "pattern",
                                    "pattern": "^[A-Z]{3}$",
                                    "severity": "error",
                                }
                            ],
                        }
                    ]
                )
            ),
            location=Location(storage="fs", path_template="/"),
        )

        validator = TabularValidator(entry)
        df = pl.DataFrame({"code": ["ABC", "invalid", "XYZ"]})
        result = validator.validate(df)

        # Should fail because "invalid" doesn't match ^[A-Z]{3}$
        assert not result.is_valid
        assert any(
            "pattern" in e.message.lower() or "code" in e.field for e in result.errors
        )

    def test_in_set_rule_rejects_invalid_data(self) -> None:
        """Column with in_set rule should reject values not in allowed set."""
        entry = TabularEntry(
            name="inset_test",
            version="1.0",
            identity=Identity(
                display_name="InSet Test",
                description="Test in_set rule",
                domain="test",
                owner=Owner(team="test", contact="test@test.com"),
            ),
            data_schema=TabularSchema(
                spec=TabularSpec(
                    columns=[
                        {
                            "name": "status",
                            "type": "string",
                            "rules": [
                                {
                                    "rule": "in_set",
                                    "allowed": ["active", "inactive"],
                                    "severity": "error",
                                }
                            ],
                        }
                    ]
                )
            ),
            location=Location(storage="fs", path_template="/"),
        )

        validator = TabularValidator(entry)
        df = pl.DataFrame({"status": ["active", "unknown", "inactive"]})
        result = validator.validate(df)

        # Should fail because "unknown" is not in allowed set
        assert not result.is_valid

    def test_valid_data_passes_all_rules(self) -> None:
        """Valid data should pass both schema and rule validation."""
        entry = TabularEntry(
            name="valid_test",
            version="1.0",
            identity=Identity(
                display_name="Valid Test",
                description="Test valid data",
                domain="test",
                owner=Owner(team="test", contact="test@test.com"),
            ),
            data_schema=TabularSchema(
                spec=TabularSpec(
                    columns=[
                        {
                            "name": "code",
                            "type": "string",
                            "rules": [
                                {
                                    "rule": "pattern",
                                    "pattern": "^[A-Z]{3}$",
                                    "severity": "error",
                                }
                            ],
                        }
                    ]
                )
            ),
            location=Location(storage="fs", path_template="/"),
        )

        validator = TabularValidator(entry)
        df = pl.DataFrame({"code": ["ABC", "XYZ", "DEF"]})
        result = validator.validate(df)

        # Should pass - all codes match pattern
        assert result.is_valid
