"""
Comprehensive tests for payload-guard Python package.
"""

import pytest
from payloadguard import (
    guard,
    Guard,
    build_shape,
    compile_shape,
    ValidationError,
    FieldConfig,
    ErrorCodes,
)


class TestBasicValidation:
    """Test basic validation functionality."""

    def test_string_type(self):
        """Test string type validation."""
        shape = guard.shape({"name": "string"})
        result = shape({"name": "John"})
        assert result == {"name": "John"}

    def test_number_type(self):
        """Test number type validation."""
        shape = guard.shape({"age": "number"})
        result = shape({"age": 30})
        assert result == {"age": 30}

    def test_boolean_type(self):
        """Test boolean type validation."""
        shape = guard.shape({"active": "boolean"})
        result = shape({"active": True})
        assert result == {"active": True}

    def test_extra_fields_removed(self):
        """Test that extra fields are removed."""
        shape = guard.shape({"name": "string"})
        result = shape({"name": "John", "extra": "field"})
        assert "extra" not in result

    def test_missing_fields_undefined(self):
        """Test that missing fields become undefined."""
        shape = guard.shape({"name": "string", "email": "string"})
        result = shape({"name": "John"})
        assert result["name"] == "John"
        assert result["email"] is None


class TestStringBuilder:
    """Test string builder functionality."""

    def test_min_length(self):
        """Test minimum length validation."""
        shape = guard.shape({"name": guard.string().min(3)})
        result = shape({"name": "Jo"})
        assert result["name"] is None

        result = shape({"name": "Joe"})
        assert result["name"] == "Joe"

    def test_max_length(self):
        """Test maximum length validation."""
        shape = guard.shape({"name": guard.string().max(5)})
        result = shape({"name": "Joseph"})
        assert result["name"] is None

        result = shape({"name": "Joe"})
        assert result["name"] == "Joe"

    def test_email_validation(self):
        """Test email validation."""
        shape = guard.shape({"email": guard.string().email()})
        result = shape({"email": "not-an-email"})
        assert result["email"] is None

        result = shape({"email": "test@example.com"})
        assert result["email"] == "test@example.com"

    def test_trim(self):
        """Test trim transformation."""
        shape = guard.shape({"name": guard.string().trim()})
        result = shape({"name": "  John  "})
        assert result["name"] == "John"

    def test_to_lower(self):
        """Test to_lower transformation."""
        shape = guard.shape({"email": guard.string().to_lower()})
        result = shape({"email": "TEST@Example.com"})
        assert result["email"] == "test@example.com"

    def test_regex(self):
        """Test regex validation."""
        shape = guard.shape({"zip": guard.string().regex(r"^\d{5}$")})
        result = shape({"zip": "12345"})
        assert result["zip"] == "12345"

        result = shape({"zip": "abcde"})
        assert result["zip"] is None


class TestNumberBuilder:
    """Test number builder functionality."""

    def test_min_value(self):
        """Test minimum value validation."""
        shape = guard.shape({"age": guard.number().min(18)})
        result = shape({"age": 17})
        assert result["age"] is None

        result = shape({"age": 18})
        assert result["age"] == 18

    def test_max_value(self):
        """Test maximum value validation."""
        shape = guard.shape({"age": guard.number().max(100)})
        result = shape({"age": 101})
        assert result["age"] is None

        result = shape({"age": 100})
        assert result["age"] == 100

    def test_integer(self):
        """Test integer validation."""
        shape = guard.shape({"count": guard.number().integer()})
        result = shape({"count": 5})
        assert result["count"] == 5

        result = shape({"count": 5.5})
        assert result["count"] is None

    def test_positive(self):
        """Test positive number validation."""
        shape = guard.shape({"value": guard.number().positive()})
        result = shape({"value": -5})
        assert result["value"] is None

        result = shape({"value": 5})
        assert result["value"] == 5


class TestNestedObjects:
    """Test nested object validation."""

    def test_basic_nested(self):
        """Test basic nested object validation."""
        shape = guard.shape(
            {
                "id": "number",
                "profile": {
                    "name": "string",
                    "email": "string",
                },
            }
        )

        result = shape(
            {
                "id": 1,
                "profile": {"name": "John", "email": "john@example.com"},
            }
        )

        assert result == {
            "id": 1,
            "profile": {"name": "John", "email": "john@example.com"},
        }

    def test_nested_with_builders(self):
        """Test nested objects with builders."""
        shape = guard.shape(
            {
                "id": "number",
                "profile": {
                    "email": guard.string().email(),
                    "age": guard.number().min(18),
                },
            }
        )

        result = shape(
            {
                "id": 1,
                "profile": {"email": "test@example.com", "age": 25},
            }
        )

        assert result["profile"]["email"] == "test@example.com"
        assert result["profile"]["age"] == 25

    def test_deep_nesting(self):
        """Test deeply nested structures."""
        shape = guard.shape(
            {
                "user": {
                    "profile": {
                        "personal": {"name": "string", "age": "number"},
                        "contact": {"email": "string", "phone": "string"},
                    }
                }
            }
        )

        result = shape(
            {
                "user": {
                    "profile": {
                        "personal": {"name": "John", "age": 30},
                        "contact": {"email": "john@example.com", "phone": "123-456-7890"},
                    }
                }
            }
        )

        assert result["user"]["profile"]["personal"]["name"] == "John"
        assert result["user"]["profile"]["contact"]["email"] == "john@example.com"

    def test_nested_sensitive_fields(self):
        """Test that sensitive fields are removed in nested objects."""
        shape = guard.shape(
            {
                "user": {
                    "name": "string",
                    "password": "string",  # Should be removed
                }
            }
        )

        result = shape({"user": {"name": "John", "password": "secret"}})
        assert "password" not in result["user"]


class TestArrays:
    """Test array validation."""

    def test_basic_array(self):
        """Test basic array validation."""
        shape = guard.shape({"tags": guard.array("string")})
        result = shape({"tags": ["tag1", "tag2", "tag3"]})
        assert result == {"tags": ["tag1", "tag2", "tag3"]}

    def test_array_of_objects(self):
        """Test array of objects."""
        shape = guard.shape(
            {
                "users": guard.array(
                    {
                        "id": "number",
                        "name": "string",
                    }
                )
            }
        )

        result = shape(
            {
                "users": [
                    {"id": 1, "name": "John"},
                    {"id": 2, "name": "Jane"},
                ]
            }
        )

        assert len(result["users"]) == 2
        assert result["users"][0]["name"] == "John"

    def test_nested_arrays(self):
        """Test nested arrays."""
        shape = guard.shape(
            {
                "posts": guard.array(
                    {
                        "id": "number",
                        "tags": guard.array("string"),
                    }
                )
            }
        )

        result = shape(
            {
                "posts": [
                    {"id": 1, "tags": ["tag1", "tag2"]},
                    {"id": 2, "tags": ["tag3"]},
                ]
            }
        )

        assert result["posts"][0]["tags"] == ["tag1", "tag2"]


class TestCustomErrors:
    """Test custom error messages."""

    def test_static_error_message(self):
        """Test static error message."""
        shape = guard.shape(
            {"email": guard.string().email().error("Please provide a valid email")}
        )

        result = shape({"email": "invalid"})
        assert result["email"] is None

    def test_dynamic_error_message(self):
        """Test dynamic error message."""

        def error_fn(value, field):
            return f"{field} must be at least 5 characters"

        shape = guard.shape(
            {"username": guard.string().min(5).error(error_fn)}
        )

        result = shape({"username": "abc"})
        assert result["username"] is None

    def test_error_codes(self):
        """Test error codes."""
        shape = guard.shape(
            {
                "age": guard.number()
                .min(18)
                .error_codes(ErrorCodes(min="AGE_TOO_YOUNG"))
            }
        )

        result = shape({"age": 15})
        assert result["age"] is None


class TestErrorCollection:
    """Test error collection functionality."""

    def test_collect_errors(self):
        """Test collecting validation errors."""
        from payloadguard.core.compiler import CompileOptions

        errors = []
        opts = CompileOptions(
            sensitive=[],
            collect_errors=True,
            errors=errors,
        )

        validator = compile_shape(
            {
                "email": FieldConfig(type="string", email=True),
                "age": FieldConfig(type="number", min=18),
            },
            opts,
        )

        validator({"email": "invalid", "age": 15})

        assert len(errors) > 0
        assert any("email" in e.field for e in errors)
        assert any("age" in e.field for e in errors)

    def test_nested_error_paths(self):
        """Test error paths for nested objects."""
        from payloadguard.core.compiler import CompileOptions

        errors = []
        opts = CompileOptions(
            sensitive=[],
            collect_errors=True,
            errors=errors,
        )

        validator = compile_shape(
            {
                "user": {
                    "profile": {
                        "email": FieldConfig(type="string", email=True),
                    }
                }
            },
            opts,
        )

        validator({"user": {"profile": {"email": "invalid"}}})

        assert any("user.profile.email" in e.field for e in errors)


class TestSensitiveFields:
    """Test sensitive field handling."""

    def test_default_sensitive_fields(self):
        """Test default sensitive fields are removed."""
        shape = guard.shape({"name": "string", "password": "string"})
        result = shape({"name": "John", "password": "secret"})
        assert "password" not in result

    def test_custom_sensitive_fields(self):
        """Test custom sensitive fields."""
        custom_guard = Guard()
        custom_guard.config(sensitive_fields=["internal_id"])

        shape = custom_guard.shape({"name": "string", "internal_id": "string"})
        result = shape({"name": "John", "internal_id": "123"})
        assert "internal_id" not in result

    def test_nested_sensitive_fields(self):
        """Test sensitive fields in nested objects."""
        shape = guard.shape(
            {
                "user": {
                    "name": "string",
                    "token": "string",  # Should be removed
                }
            }
        )

        result = shape({"user": {"name": "John", "token": "abc123"}})
        assert "token" not in result["user"]


class TestFieldAliasing:
    """Test field aliasing (from_field)."""

    def test_basic_aliasing(self):
        """Test basic field aliasing."""
        shape = guard.shape({"id": guard.string().from_("__id")})
        result = shape({"__id": "123"})
        assert result["id"] == "123"

    def test_nested_aliasing(self):
        """Test nested field aliasing."""
        shape = guard.shape(
            {
                "user": {
                    "id": guard.string().from_("_id"),
                    "name": "string",
                }
            }
        )

        result = shape({"user": {"_id": "123", "name": "John"}})
        assert result["user"]["id"] == "123"


class TestTransformations:
    """Test data transformations."""

    def test_custom_transform(self):
        """Test custom transformation."""
        shape = guard.shape(
            {"name": guard.string().transform(lambda x: x.upper())}
        )
        result = shape({"name": "john"})
        assert result["name"] == "JOHN"

    def test_chained_transforms(self):
        """Test chained transformations."""
        shape = guard.shape(
            {
                "email": guard.string()
                .trim()
                .to_lower()
            }
        )
        result = shape({"email": "  TEST@Example.com  "})
        assert result["email"] == "test@example.com"

    def test_number_transform(self):
        """Test number transformation."""
        shape = guard.shape(
            {"value": guard.number().transform(lambda x: x * 2)}
        )
        result = shape({"value": 5})
        assert result["value"] == 10


class TestEdgeCases:
    """Test edge cases."""

    def test_null_input(self):
        """Test null input handling."""
        shape = guard.shape({"name": "string"})
        result = shape(None)
        assert result == {}

    def test_empty_object(self):
        """Test empty object handling."""
        shape = guard.shape({"name": "string"})
        result = shape({})
        assert result == {"name": None}

    def test_circular_reference(self):
        """Test circular reference handling."""
        shape = guard.shape({"name": "string"})

        circular = {"name": "test"}
        circular["self"] = circular

        result = shape(circular)
        assert result["name"] == "test"

    def test_default_value(self):
        """Test default value."""
        shape = guard.shape({"name": guard.string().default("Anonymous")})
        result = shape({})
        assert result["name"] == "Anonymous"

    def test_required_field(self):
        """Test required field."""
        shape = guard.shape({"name": guard.string().required()})
        result = shape({})
        assert result["name"] is None


class TestRealWorldScenarios:
    """Test real-world scenarios."""

    def test_user_registration(self):
        """Test user registration validation."""
        user_shape = guard.shape(
            {
                "username": guard.string().min(3).max(20),
                "email": guard.string().email(),
                "password": guard.string().min(8),
                "age": guard.number().min(18),
            }
        )

        result = user_shape(
            {
                "username": "john_doe",
                "email": "john@example.com",
                "password": "secret123",
                "age": 25,
                "extra": "field",
            }
        )

        assert result["username"] == "john_doe"
        assert result["email"] == "john@example.com"
        assert "extra" not in result

    def test_api_response(self):
        """Test API response filtering."""
        response_shape = guard.shape(
            {
                "success": "boolean",
                "data": {
                    "users": guard.array(
                        {
                            "id": "number",
                            "name": "string",
                            "email": "string",
                        }
                    ),
                    "total": "number",
                },
            }
        )

        result = response_shape(
            {
                "success": True,
                "data": {
                    "users": [
                        {"id": 1, "name": "John", "email": "john@example.com"},
                    ],
                    "total": 100,
                },
            }
        )

        assert result["success"] is True
        assert len(result["data"]["users"]) == 1

    def test_ecommerce_order(self):
        """Test e-commerce order validation."""
        order_shape = guard.shape(
            {
                "orderId": "string",
                "customer": {
                    "name": "string",
                    "email": guard.string().email(),
                    "address": {
                        "street": "string",
                        "city": "string",
                        "zipCode": guard.string().regex(r"^\d{5}$"),
                    },
                },
                "items": guard.array(
                    {
                        "productId": "string",
                        "quantity": guard.number().min(1).max(100),
                        "price": guard.number().min(0),
                    }
                ),
            }
        )

        result = order_shape(
            {
                "orderId": "ORD-123",
                "customer": {
                    "name": "John Doe",
                    "email": "john@example.com",
                    "address": {
                        "street": "123 Main St",
                        "city": "NYC",
                        "zipCode": "10001",
                    },
                },
                "items": [
                    {"productId": "PROD-1", "quantity": 2, "price": 29.99}
                ],
            }
        )

        assert result["orderId"] == "ORD-123"
        assert result["customer"]["email"] == "john@example.com"
        assert len(result["items"]) == 1


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
