"""
Unit tests for BaseSectionConfig class with Pydantic validation.

This module tests the Pydantic-based BaseSectionConfig functionality
that all configuration classes inherit.
"""

import unittest

from pydantic import Field, ValidationError, field_validator

from data_exchange_agent.config.sections.base_section_config import BaseSectionConfig


class TestBaseSectionConfig(unittest.TestCase):
    """Test suite for BaseSectionConfig class."""

    def test_creates_instances(self):
        """Test that BaseSectionConfig subclass allows instance creation."""

        class TestConfig(BaseSectionConfig):
            name: str = "default"

        config = TestConfig()
        self.assertIsInstance(config, TestConfig)
        self.assertEqual(config.name, "default")

    def test_creates_instance_with_values(self):
        """Test that BaseSectionConfig subclass accepts values."""

        class TestConfig(BaseSectionConfig):
            name: str

        config = TestConfig(name="test_value")
        self.assertEqual(config.name, "test_value")

    def test_validates_on_instantiation(self):
        """Test that validation is called during instantiation."""

        class TestConfig(BaseSectionConfig):
            value: int = Field(..., ge=0)

        # Valid value
        config = TestConfig(value=10)
        self.assertEqual(config.value, 10)

        # Invalid value
        with self.assertRaises(ValidationError):
            TestConfig(value=-1)

    def test_required_field_missing_raises_validation_error(self):
        """Test that missing required field raises ValidationError."""

        class TestConfig(BaseSectionConfig):
            required_field: str

        with self.assertRaises(ValidationError) as context:
            TestConfig()

        errors = context.exception.errors()
        self.assertEqual(len(errors), 1)
        self.assertEqual(errors[0]["type"], "missing")
        self.assertIn("required_field", errors[0]["loc"])

    def test_getitem_with_valid_attribute(self):
        """Test __getitem__ with a valid attribute."""

        class TestConfig(BaseSectionConfig):
            test_attr: str = "test_value"

        config = TestConfig()
        self.assertEqual(config["test_attr"], "test_value")

    def test_getitem_with_missing_attribute_raises_keyerror(self):
        """Test __getitem__ raises KeyError for missing attributes."""

        class TestConfig(BaseSectionConfig):
            pass

        config = TestConfig()

        with self.assertRaises(KeyError) as context:
            _ = config["nonexistent"]

        self.assertIn("'nonexistent' not found in configuration", str(context.exception))

    def test_getitem_with_private_attribute_raises_keyerror(self):
        """Test __getitem__ raises KeyError for private attributes."""

        class TestConfig(BaseSectionConfig):
            name: str = "test"

        config = TestConfig()

        with self.assertRaises(KeyError) as context:
            _ = config["_private"]

        self.assertIn("'_private' not found in configuration.", str(context.exception))

    def test_get_with_default(self):
        """Test get method returns default for missing keys."""

        class TestConfig(BaseSectionConfig):
            name: str = "test"

        config = TestConfig()
        result = config.get("nonexistent", "default_value")
        self.assertEqual(result, "default_value")

    def test_get_with_existing_key(self):
        """Test get method returns value for existing keys."""

        class TestConfig(BaseSectionConfig):
            name: str = "test"

        config = TestConfig()
        result = config.get("name", "default_value")
        self.assertEqual(result, "test")

    def test_contains_with_existing_attribute(self):
        """Test __contains__ returns True for existing attributes."""

        class TestConfig(BaseSectionConfig):
            name: str = "test"

        config = TestConfig()
        self.assertTrue("name" in config)

    def test_contains_with_missing_attribute(self):
        """Test __contains__ returns False for missing attributes."""

        class TestConfig(BaseSectionConfig):
            name: str = "test"

        config = TestConfig()
        self.assertFalse("nonexistent" in config)

    def test_contains_with_private_attribute(self):
        """Test __contains__ returns False for private attributes."""

        class TestConfig(BaseSectionConfig):
            name: str = "test"

        config = TestConfig()
        self.assertFalse("_private" in config)


class TestPydanticValidation(unittest.TestCase):
    """Test suite for Pydantic validation in BaseSectionConfig."""

    def test_field_type_validation(self):
        """Test that field type validation works."""

        class TestConfig(BaseSectionConfig):
            count: int

        # Valid type
        config = TestConfig(count=42)
        self.assertEqual(config.count, 42)

        # Invalid type - Pydantic will try to coerce
        config = TestConfig(count="42")
        self.assertEqual(config.count, 42)

        # Invalid type that can't be coerced
        with self.assertRaises(ValidationError):
            TestConfig(count="not_a_number")

    def test_field_constraints(self):
        """Test that Field constraints work."""

        class TestConfig(BaseSectionConfig):
            value: int = Field(..., ge=0, le=100)

        # Valid value
        config = TestConfig(value=50)
        self.assertEqual(config.value, 50)

        # Below minimum
        with self.assertRaises(ValidationError):
            TestConfig(value=-1)

        # Above maximum
        with self.assertRaises(ValidationError):
            TestConfig(value=101)

    def test_custom_field_validator(self):
        """Test that custom field validators work."""

        class TestConfig(BaseSectionConfig):
            name: str

            @field_validator("name")
            @classmethod
            def validate_name(cls, v: str) -> str:
                if not v.strip():
                    raise ValueError("Name cannot be empty or whitespace")
                return v

        # Valid value
        config = TestConfig(name="valid_name")
        self.assertEqual(config.name, "valid_name")

        # Invalid value
        with self.assertRaises(ValidationError) as context:
            TestConfig(name="   ")

        errors = context.exception.errors()
        self.assertIn("Name cannot be empty or whitespace", str(errors))

    def test_optional_field_with_none(self):
        """Test that optional fields accept None."""

        class TestConfig(BaseSectionConfig):
            optional_field: str | None = None

        config = TestConfig()
        self.assertIsNone(config.optional_field)

        config = TestConfig(optional_field="value")
        self.assertEqual(config.optional_field, "value")

    def test_multiple_validation_errors(self):
        """Test that multiple validation errors are collected."""

        class TestConfig(BaseSectionConfig):
            field1: int = Field(..., ge=0)
            field2: int = Field(..., ge=0)
            field3: str

        with self.assertRaises(ValidationError) as context:
            TestConfig(field1=-1, field2=-2)

        # Should have errors for field1, field2, and missing field3
        errors = context.exception.errors()
        self.assertGreaterEqual(len(errors), 3)


class TestInheritance(unittest.TestCase):
    """Test suite for BaseSectionConfig inheritance behavior."""

    def test_subclass_inherits_fields(self):
        """Test that subclasses inherit parent fields."""

        class ParentConfig(BaseSectionConfig):
            parent_field: str = "parent_value"

        class ChildConfig(ParentConfig):
            child_field: str = "child_value"

        config = ChildConfig()
        self.assertEqual(config.parent_field, "parent_value")
        self.assertEqual(config.child_field, "child_value")

    def test_subclass_can_override_fields(self):
        """Test that subclasses can override parent fields."""

        class ParentConfig(BaseSectionConfig):
            field: str = "parent_default"

        class ChildConfig(ParentConfig):
            field: str = "child_default"

        config = ChildConfig()
        self.assertEqual(config.field, "child_default")

    def test_subclass_inherits_getitem(self):
        """Test that subclasses inherit __getitem__ functionality."""

        class ParentConfig(BaseSectionConfig):
            pass

        class ChildConfig(ParentConfig):
            test_attr: str = "test"

        config = ChildConfig()
        self.assertEqual(config["test_attr"], "test")

    def test_subclass_inherits_validators(self):
        """Test that subclasses inherit validators from parent."""

        class ParentConfig(BaseSectionConfig):
            name: str

            @field_validator("name")
            @classmethod
            def validate_name(cls, v: str) -> str:
                if len(v) < 3:
                    raise ValueError("Name must be at least 3 characters")
                return v

        class ChildConfig(ParentConfig):
            extra_field: str = "extra"

        # Valid name
        config = ChildConfig(name="valid")
        self.assertEqual(config.name, "valid")

        # Invalid name
        with self.assertRaises(ValidationError):
            ChildConfig(name="ab")


class TestMaskSensitiveData(unittest.TestCase):
    """Test suite for _mask_sensitive_data utility method."""

    def test_mask_short_data(self):
        """Test masking of short data (2 chars or less)."""
        self.assertEqual(BaseSectionConfig._mask_sensitive_data("ab"), "**")
        self.assertEqual(BaseSectionConfig._mask_sensitive_data("a"), "*")

    def test_mask_medium_data(self):
        """Test masking of medium length data."""
        result = BaseSectionConfig._mask_sensitive_data("password")
        self.assertTrue(result.startswith("pas"))
        self.assertTrue(result.endswith("*****"))

    def test_mask_long_data(self):
        """Test masking of longer data."""
        result = BaseSectionConfig._mask_sensitive_data("verylongsecretpassword")
        self.assertTrue(result.startswith("ver"))
        self.assertIn("*", result)


if __name__ == "__main__":
    unittest.main()
