"""
Tests for Park serializers.

Following Django styleguide pattern: test__<context>__<action>__<expected_outcome>
"""

from unittest.mock import Mock

import pytest
from django.test import TestCase

from apps.api.v1.parks.serializers import (
    HybridParkSerializer,
    ParkPhotoApprovalInputSerializer,
    ParkPhotoCreateInputSerializer,
    ParkPhotoListOutputSerializer,
    ParkPhotoOutputSerializer,
    ParkPhotoSerializer,
    ParkPhotoStatsOutputSerializer,
    ParkPhotoUpdateInputSerializer,
    ParkSerializer,
)
from tests.factories import (
    CloudflareImageFactory,
    ParkFactory,
    ParkPhotoFactory,
    UserFactory,
)


@pytest.mark.django_db
class TestParkPhotoOutputSerializer(TestCase):
    """Tests for ParkPhotoOutputSerializer."""

    def test__serialize__valid_photo__returns_all_fields(self):
        """Test serializing a park photo returns all expected fields."""
        user = UserFactory()
        park = ParkFactory()
        image = CloudflareImageFactory()
        photo = ParkPhotoFactory(
            park=park,
            uploaded_by=user,
            image=image,
            caption="Test caption",
            alt_text="Test alt text",
            is_primary=True,
            is_approved=True,
        )

        serializer = ParkPhotoOutputSerializer(photo)
        data = serializer.data

        assert "id" in data
        assert data["caption"] == "Test caption"
        assert data["alt_text"] == "Test alt text"
        assert data["is_primary"] is True
        assert data["is_approved"] is True
        assert data["uploaded_by_username"] == user.username
        assert data["park_slug"] == park.slug
        assert data["park_name"] == park.name

    def test__serialize__photo_with_image__returns_image_url(self):
        """Test serializing a photo with image returns URL."""
        photo = ParkPhotoFactory()

        serializer = ParkPhotoOutputSerializer(photo)
        data = serializer.data

        assert "image_url" in data
        assert "image_variants" in data

    def test__serialize__photo_without_image__returns_none_for_image_fields(self):
        """Test serializing photo without image returns None for image fields."""
        photo = ParkPhotoFactory()
        photo.image = None
        photo.save()

        serializer = ParkPhotoOutputSerializer(photo)
        data = serializer.data

        assert data["image_url"] is None
        assert data["image_variants"] == {}

    def test__get_file_size__photo_with_image__returns_file_size(self):
        """Test get_file_size method returns file size."""
        photo = ParkPhotoFactory()

        serializer = ParkPhotoOutputSerializer(photo)

        # file_size comes from the model property
        assert "file_size" in serializer.data

    def test__get_dimensions__photo_with_image__returns_dimensions(self):
        """Test get_dimensions method returns [width, height]."""
        photo = ParkPhotoFactory()

        serializer = ParkPhotoOutputSerializer(photo)

        assert "dimensions" in serializer.data

    def test__get_image_variants__photo_with_image__returns_variant_urls(self):
        """Test get_image_variants returns all variant URLs."""
        image = CloudflareImageFactory()
        photo = ParkPhotoFactory(image=image)

        serializer = ParkPhotoOutputSerializer(photo)
        data = serializer.data

        if photo.image:
            variants = data["image_variants"]
            assert "thumbnail" in variants
            assert "medium" in variants
            assert "large" in variants
            assert "public" in variants


@pytest.mark.django_db
class TestParkPhotoCreateInputSerializer(TestCase):
    """Tests for ParkPhotoCreateInputSerializer."""

    def test__serialize__valid_data__returns_expected_fields(self):
        """Test serializing valid create data."""
        image = CloudflareImageFactory()
        data = {
            "image": image.pk,
            "caption": "New photo caption",
            "alt_text": "Description of the image",
            "is_primary": False,
        }

        serializer = ParkPhotoCreateInputSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert "caption" in serializer.validated_data
        assert "alt_text" in serializer.validated_data
        assert "is_primary" in serializer.validated_data

    def test__validate__missing_required_fields__returns_error(self):
        """Test validation fails with missing required fields."""
        data = {}

        serializer = ParkPhotoCreateInputSerializer(data=data)

        # image is required since it's not in read_only_fields
        assert not serializer.is_valid()

    def test__meta__fields__includes_expected_fields(self):
        """Test Meta.fields includes the expected input fields."""
        expected_fields = ["image", "caption", "alt_text", "is_primary"]

        assert list(ParkPhotoCreateInputSerializer.Meta.fields) == expected_fields


@pytest.mark.django_db
class TestParkPhotoUpdateInputSerializer(TestCase):
    """Tests for ParkPhotoUpdateInputSerializer."""

    def test__serialize__valid_data__returns_expected_fields(self):
        """Test serializing valid update data."""
        data = {
            "caption": "Updated caption",
            "alt_text": "Updated alt text",
            "is_primary": True,
        }

        serializer = ParkPhotoUpdateInputSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["caption"] == "Updated caption"
        assert serializer.validated_data["alt_text"] == "Updated alt text"
        assert serializer.validated_data["is_primary"] is True

    def test__serialize__partial_update__validates_partial_data(self):
        """Test partial update with only some fields."""
        photo = ParkPhotoFactory()
        data = {"caption": "Only caption updated"}

        serializer = ParkPhotoUpdateInputSerializer(photo, data=data, partial=True)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["caption"] == "Only caption updated"

    def test__meta__fields__excludes_image_field(self):
        """Test Meta.fields excludes image field for updates."""
        expected_fields = ["caption", "alt_text", "is_primary"]

        assert list(ParkPhotoUpdateInputSerializer.Meta.fields) == expected_fields


@pytest.mark.django_db
class TestParkPhotoListOutputSerializer(TestCase):
    """Tests for ParkPhotoListOutputSerializer."""

    def test__serialize__photo__returns_list_fields_only(self):
        """Test serializing returns only list-appropriate fields."""
        user = UserFactory()
        photo = ParkPhotoFactory(uploaded_by=user)

        serializer = ParkPhotoListOutputSerializer(photo)
        data = serializer.data

        assert "id" in data
        assert "image" in data
        assert "caption" in data
        assert "is_primary" in data
        assert "is_approved" in data
        assert "created_at" in data
        assert "uploaded_by_username" in data
        # Should NOT include detailed fields
        assert "image_variants" not in data
        assert "file_size" not in data
        assert "dimensions" not in data

    def test__serialize__multiple_photos__returns_list(self):
        """Test serializing multiple photos returns a list."""
        photos = [ParkPhotoFactory() for _ in range(3)]

        serializer = ParkPhotoListOutputSerializer(photos, many=True)

        assert len(serializer.data) == 3

    def test__meta__all_fields_read_only(self):
        """Test all fields are read-only for list serializer."""
        assert ParkPhotoListOutputSerializer.Meta.read_only_fields == ParkPhotoListOutputSerializer.Meta.fields


class TestParkPhotoApprovalInputSerializer(TestCase):
    """Tests for ParkPhotoApprovalInputSerializer."""

    def test__validate__valid_photo_ids__returns_validated_data(self):
        """Test validation with valid photo IDs."""
        data = {
            "photo_ids": [1, 2, 3],
            "approve": True,
        }

        serializer = ParkPhotoApprovalInputSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["photo_ids"] == [1, 2, 3]
        assert serializer.validated_data["approve"] is True

    def test__validate__approve_default__defaults_to_true(self):
        """Test approve field defaults to True."""
        data = {"photo_ids": [1, 2]}

        serializer = ParkPhotoApprovalInputSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["approve"] is True

    def test__validate__empty_photo_ids__is_valid(self):
        """Test empty photo_ids list is valid."""
        data = {"photo_ids": [], "approve": False}

        serializer = ParkPhotoApprovalInputSerializer(data=data)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["photo_ids"] == []

    def test__validate__missing_photo_ids__returns_error(self):
        """Test validation fails without photo_ids."""
        data = {"approve": True}

        serializer = ParkPhotoApprovalInputSerializer(data=data)

        assert not serializer.is_valid()
        assert "photo_ids" in serializer.errors

    def test__validate__invalid_photo_ids__returns_error(self):
        """Test validation fails with non-integer photo IDs."""
        data = {"photo_ids": ["invalid", "ids"]}

        serializer = ParkPhotoApprovalInputSerializer(data=data)

        assert not serializer.is_valid()


class TestParkPhotoStatsOutputSerializer(TestCase):
    """Tests for ParkPhotoStatsOutputSerializer."""

    def test__serialize__stats_dict__returns_all_fields(self):
        """Test serializing stats dictionary."""
        stats = {
            "total_photos": 100,
            "approved_photos": 80,
            "pending_photos": 20,
            "has_primary": True,
            "recent_uploads": 5,
        }

        serializer = ParkPhotoStatsOutputSerializer(data=stats)

        assert serializer.is_valid(), serializer.errors
        assert serializer.validated_data["total_photos"] == 100
        assert serializer.validated_data["approved_photos"] == 80
        assert serializer.validated_data["pending_photos"] == 20
        assert serializer.validated_data["has_primary"] is True
        assert serializer.validated_data["recent_uploads"] == 5

    def test__validate__missing_fields__returns_error(self):
        """Test validation fails with missing stats fields."""
        stats = {"total_photos": 100}  # Missing other required fields

        serializer = ParkPhotoStatsOutputSerializer(data=stats)

        assert not serializer.is_valid()


@pytest.mark.django_db
class TestHybridParkSerializer(TestCase):
    """Tests for HybridParkSerializer."""

    def test__serialize__park_with_all_fields__returns_complete_data(self):
        """Test serializing park with all fields populated."""
        park = ParkFactory()

        serializer = HybridParkSerializer(park)
        data = serializer.data

        assert "id" in data
        assert "name" in data
        assert "slug" in data
        assert "status" in data
        assert "operator_name" in data

    def test__serialize__park_without_location__returns_null_location_fields(self):
        """Test serializing park without location returns null for location fields."""
        park = ParkFactory()
        # Remove location if it exists
        if hasattr(park, "location") and park.location:
            park.location.delete()

        serializer = HybridParkSerializer(park)
        data = serializer.data

        # Location fields should be None when no location
        assert "city" in data
        assert "state" in data
        assert "country" in data

    def test__get_city__park_with_location__returns_city(self):
        """Test get_city returns city from location."""
        park = ParkFactory()

        # Create a mock location
        mock_location = Mock()
        mock_location.city = "Orlando"
        mock_location.state = "FL"
        mock_location.country = "USA"
        mock_location.continent = "North America"
        mock_location.coordinates = [-81.3792, 28.5383]  # [lon, lat]

        park.location = mock_location

        serializer = HybridParkSerializer(park)

        assert serializer.get_city(park) == "Orlando"

    def test__get_latitude__park_with_coordinates__returns_latitude(self):
        """Test get_latitude returns correct value from coordinates."""
        park = ParkFactory()

        mock_location = Mock()
        mock_location.coordinates = [-81.3792, 28.5383]  # [lon, lat]
        park.location = mock_location

        serializer = HybridParkSerializer(park)

        # Latitude is index 1 in PostGIS [lon, lat] format
        assert serializer.get_latitude(park) == 28.5383

    def test__get_longitude__park_with_coordinates__returns_longitude(self):
        """Test get_longitude returns correct value from coordinates."""
        park = ParkFactory()

        mock_location = Mock()
        mock_location.coordinates = [-81.3792, 28.5383]  # [lon, lat]
        park.location = mock_location

        serializer = HybridParkSerializer(park)

        # Longitude is index 0 in PostGIS [lon, lat] format
        assert serializer.get_longitude(park) == -81.3792

    def test__get_banner_image_url__park_with_banner__returns_url(self):
        """Test get_banner_image_url returns URL when banner exists."""
        park = ParkFactory()

        mock_image = Mock()
        mock_image.url = "https://example.com/banner.jpg"

        mock_banner = Mock()
        mock_banner.image = mock_image

        park.banner_image = mock_banner

        serializer = HybridParkSerializer(park)

        assert serializer.get_banner_image_url(park) == "https://example.com/banner.jpg"

    def test__get_banner_image_url__park_without_banner__returns_none(self):
        """Test get_banner_image_url returns None when no banner."""
        park = ParkFactory()
        park.banner_image = None

        serializer = HybridParkSerializer(park)

        assert serializer.get_banner_image_url(park) is None

    def test__meta__all_fields_read_only(self):
        """Test all fields in HybridParkSerializer are read-only."""
        assert HybridParkSerializer.Meta.read_only_fields == HybridParkSerializer.Meta.fields


@pytest.mark.django_db
class TestParkSerializer(TestCase):
    """Tests for ParkSerializer (legacy)."""

    def test__serialize__park__returns_basic_fields(self):
        """Test serializing park returns basic fields."""
        park = ParkFactory()

        serializer = ParkSerializer(park)
        data = serializer.data

        assert "id" in data
        assert "name" in data
        assert "slug" in data
        assert "status" in data
        assert "website" in data

    def test__serialize__multiple_parks__returns_list(self):
        """Test serializing multiple parks returns a list."""
        parks = [ParkFactory() for _ in range(3)]

        serializer = ParkSerializer(parks, many=True)

        assert len(serializer.data) == 3


@pytest.mark.django_db
class TestParkPhotoSerializer(TestCase):
    """Tests for legacy ParkPhotoSerializer."""

    def test__serialize__photo__returns_legacy_fields(self):
        """Test serializing photo returns legacy field set."""
        photo = ParkPhotoFactory()

        serializer = ParkPhotoSerializer(photo)
        data = serializer.data

        assert "id" in data
        assert "image" in data
        assert "caption" in data
        assert "alt_text" in data
        assert "is_primary" in data

    def test__meta__fields__matches_legacy_format(self):
        """Test Meta.fields matches legacy format."""
        expected_fields = (
            "id",
            "image",
            "caption",
            "alt_text",
            "is_primary",
            "uploaded_at",
            "uploaded_by",
        )

        assert ParkPhotoSerializer.Meta.fields == expected_fields
