"""Tests for Pydantic models."""

from datetime import UTC, datetime
from uuid import uuid4

import pytest
from pydantic import ValidationError

from ingress_recipient.types import RecipientData, RecipientMetadata, RecipientResponse


class TestRecipientMetadata:
    """Tests for RecipientMetadata model."""

    def test_valid_metadata_required_only(self):
        """Test valid metadata with required fields only."""
        metadata = RecipientMetadata(
            ingress_timestamp=datetime.now(UTC),
        )

        assert metadata.source_ip is None
        assert metadata.transformations_applied is None
        assert isinstance(metadata.ingress_timestamp, datetime)

    def test_valid_metadata_with_optionals(self, valid_metadata_with_optionals):
        """Test metadata with all optional fields."""
        metadata = RecipientMetadata(**valid_metadata_with_optionals)

        assert metadata.source_ip == "192.168.1.100"
        assert len(metadata.transformations_applied) == 2
        assert "normalize" in metadata.transformations_applied
        assert "enrich" in metadata.transformations_applied

    def test_missing_required_ingress_timestamp(self):
        """Test that missing ingress_timestamp raises ValidationError."""
        with pytest.raises(ValidationError) as exc_info:
            RecipientMetadata(source_ip="192.168.1.100")

        errors = exc_info.value.errors()
        assert any("ingress_timestamp" in str(err["loc"]) for err in errors)

    def test_invalid_timestamp_type(self):
        """Test that invalid timestamp type raises ValidationError."""
        with pytest.raises(ValidationError):
            RecipientMetadata(ingress_timestamp="not-a-datetime-object")

    def test_transformations_applied_not_array(self):
        """Test that non-array transformations_applied raises ValidationError."""
        with pytest.raises(ValidationError):
            RecipientMetadata(
                ingress_timestamp=datetime.now(UTC),
                transformations_applied="not-an-array",
            )


class TestRecipientData:
    """Tests for RecipientData model."""

    def test_valid_recipient_data(self, valid_recipient_data):
        """Test valid recipient data."""
        data = RecipientData(**valid_recipient_data)

        assert data.channel_id == "ch_maritime_telemetry"
        assert isinstance(data.tenant_id, type(uuid4()))
        assert isinstance(data.timestamp, datetime)
        assert data.data["vessel_id"] == "v_12345"
        assert data.data["latitude"] == 59.9139
        assert isinstance(data.metadata, RecipientMetadata)

    def test_valid_data_with_empty_data_object(self):
        """Test valid data with empty data object."""
        data = RecipientData(
            channel_id="ch_test",
            tenant_id=uuid4(),
            timestamp=datetime.now(UTC),
            data={},
            metadata=RecipientMetadata(ingress_timestamp=datetime.now(UTC)),
        )

        assert data.channel_id == "ch_test"
        assert data.data == {}

    def test_valid_data_with_nested_objects(self):
        """Test valid data with nested objects in data field."""
        data = RecipientData(
            channel_id="ch_test",
            tenant_id=uuid4(),
            timestamp=datetime.now(UTC),
            data={
                "vessel": {"id": "v_123", "name": "Test Vessel"},
                "position": {"lat": 59.9139, "lng": 10.7522},
            },
            metadata=RecipientMetadata(ingress_timestamp=datetime.now(UTC)),
        )

        assert data.data["vessel"]["id"] == "v_123"
        assert data.data["position"]["lat"] == 59.9139

    def test_empty_channel_id_rejected(self):
        """Test that empty channel_id is rejected."""
        with pytest.raises(ValidationError) as exc_info:
            RecipientData(
                channel_id="",
                tenant_id=uuid4(),
                timestamp=datetime.now(UTC),
                data={},
                metadata=RecipientMetadata(ingress_timestamp=datetime.now(UTC)),
            )

        errors = exc_info.value.errors()
        assert any("channel_id" in str(err) for err in errors)

    def test_whitespace_only_channel_id_rejected(self):
        """Test that whitespace-only channel_id is rejected."""
        with pytest.raises(ValidationError) as exc_info:
            RecipientData(
                channel_id="   ",
                tenant_id=uuid4(),
                timestamp=datetime.now(UTC),
                data={},
                metadata=RecipientMetadata(ingress_timestamp=datetime.now(UTC)),
            )

        errors = exc_info.value.errors()
        assert any("channel_id" in str(err) or "whitespace" in str(err) for err in errors)

    def test_invalid_tenant_id_rejected(self):
        """Test that invalid UUID is rejected."""
        with pytest.raises(ValidationError) as exc_info:
            RecipientData(
                channel_id="ch_test",
                tenant_id="not-a-uuid",  # type: ignore
                timestamp=datetime.now(UTC),
                data={},
                metadata=RecipientMetadata(ingress_timestamp=datetime.now(UTC)),
            )

        errors = exc_info.value.errors()
        assert any("tenant_id" in str(err["loc"]) for err in errors)

    def test_invalid_timestamp_rejected(self):
        """Test that invalid timestamp is rejected."""
        with pytest.raises(ValidationError):
            RecipientData(
                channel_id="ch_test",
                tenant_id=uuid4(),
                timestamp="not-a-datetime",  # type: ignore
                data={},
                metadata=RecipientMetadata(ingress_timestamp=datetime.now(UTC)),
            )

    def test_missing_required_fields(self):
        """Test that missing required fields raises ValidationError."""
        with pytest.raises(ValidationError):
            RecipientData(channel_id="ch_test")  # type: ignore

    def test_invalid_metadata_rejected(self):
        """Test that invalid metadata is rejected."""
        with pytest.raises(ValidationError):
            RecipientData(
                channel_id="ch_test",
                tenant_id=uuid4(),
                timestamp=datetime.now(UTC),
                data={},
                metadata={"source_ip": "192.168.1.1"},  # Missing required ingress_timestamp
            )

    def test_from_dict_with_iso_strings(self, valid_recipient_data_dict):
        """Test parsing from dict with ISO string dates."""
        data = RecipientData.model_validate(valid_recipient_data_dict)

        assert data.channel_id == "ch_maritime_telemetry"
        assert isinstance(data.tenant_id, type(uuid4()))
        assert isinstance(data.timestamp, datetime)
        assert data.data["vessel_id"] == "v_12345"


class TestRecipientResponse:
    """Tests for RecipientResponse model."""

    def test_accepted_status(self):
        """Test valid 'accepted' status."""
        response = RecipientResponse(status="accepted")

        assert response.status == "accepted"
        assert response.message is None

    def test_rejected_status(self):
        """Test valid 'rejected' status."""
        response = RecipientResponse(status="rejected")

        assert response.status == "rejected"
        assert response.message is None

    def test_with_message(self):
        """Test response with message."""
        response = RecipientResponse(
            status="accepted", message="Data processed successfully"
        )

        assert response.status == "accepted"
        assert response.message == "Data processed successfully"

    def test_invalid_status_rejected(self):
        """Test that invalid status is rejected."""
        with pytest.raises(ValidationError) as exc_info:
            RecipientResponse(status="pending")

        errors = exc_info.value.errors()
        assert any("status" in str(err) for err in errors)

    def test_missing_status_rejected(self):
        """Test that missing status is rejected."""
        with pytest.raises(ValidationError):
            RecipientResponse(message="Some message")  # type: ignore

    def test_empty_status_rejected(self):
        """Test that empty status is rejected."""
        with pytest.raises(ValidationError):
            RecipientResponse(status="")

    def test_status_case_sensitive(self):
        """Test that status is case-sensitive."""
        with pytest.raises(ValidationError):
            RecipientResponse(status="Accepted")  # Capital A

        with pytest.raises(ValidationError):
            RecipientResponse(status="ACCEPTED")  # All caps
