"""
Tests for Park managers and querysets.

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

import pytest
from django.test import TestCase

from apps.parks.managers import (
    ParkQuerySet,
)
from apps.parks.models import Company, Park, ParkArea, ParkReview
from tests.factories import (
    CoasterFactory,
    ManufacturerCompanyFactory,
    OperatorCompanyFactory,
    ParkAreaFactory,
    ParkFactory,
    ParkReviewFactory,
    RideFactory,
    UserFactory,
)


@pytest.mark.django_db
class TestParkQuerySet(TestCase):
    """Tests for ParkQuerySet."""

    def test__with_complete_stats__annotates_ride_counts(self):
        """Test with_complete_stats adds ride count annotations."""
        park = ParkFactory()
        RideFactory(park=park, category="TR")
        RideFactory(park=park, category="TR")
        CoasterFactory(park=park, category="RC")

        result = Park.objects.with_complete_stats().get(pk=park.pk)

        assert result.ride_count_calculated == 3
        assert result.coaster_count_calculated == 1

    def test__with_complete_stats__annotates_review_stats(self):
        """Test with_complete_stats adds review statistics."""
        park = ParkFactory()
        user1 = UserFactory()
        user2 = UserFactory()
        ParkReviewFactory(park=park, user=user1, is_published=True, rating=8)
        ParkReviewFactory(park=park, user=user2, is_published=True, rating=6)

        result = Park.objects.with_complete_stats().get(pk=park.pk)

        assert result.review_count == 2
        assert result.average_rating_calculated == 7.0

    def test__with_complete_stats__excludes_unpublished_reviews(self):
        """Test review stats exclude unpublished reviews."""
        park = ParkFactory()
        user1 = UserFactory()
        user2 = UserFactory()
        ParkReviewFactory(park=park, user=user1, is_published=True, rating=10)
        ParkReviewFactory(park=park, user=user2, is_published=False, rating=2)

        result = Park.objects.with_complete_stats().get(pk=park.pk)

        assert result.review_count == 1
        assert result.average_rating_calculated == 10.0

    def test__optimized_for_list__returns_prefetched_data(self):
        """Test optimized_for_list prefetches related data."""
        ParkFactory()
        ParkFactory()

        queryset = Park.objects.optimized_for_list()

        # Should have prefetch cache populated
        assert queryset.count() == 2

    def test__by_operator__filters_by_operator_id(self):
        """Test by_operator filters parks by operator."""
        operator = OperatorCompanyFactory()
        other_operator = OperatorCompanyFactory()
        park1 = ParkFactory(operator=operator)
        park2 = ParkFactory(operator=other_operator)

        result = Park.objects.by_operator(operator_id=operator.pk)

        assert park1 in result
        assert park2 not in result

    def test__by_property_owner__filters_by_owner_id(self):
        """Test by_property_owner filters parks by property owner."""
        owner = OperatorCompanyFactory()
        park1 = ParkFactory(property_owner=owner)
        park2 = ParkFactory()

        result = Park.objects.get_queryset().by_property_owner(owner_id=owner.pk)

        assert park1 in result
        assert park2 not in result

    def test__with_minimum_coasters__filters_by_coaster_count(self):
        """Test with_minimum_coasters filters parks with enough coasters."""
        park1 = ParkFactory()
        park2 = ParkFactory()

        # Add 5 coasters to park1
        for _ in range(5):
            CoasterFactory(park=park1)

        # Add only 2 coasters to park2
        for _ in range(2):
            CoasterFactory(park=park2)

        result = Park.objects.with_minimum_coasters(min_coasters=5)

        assert park1 in result
        assert park2 not in result

    def test__large_parks__filters_by_size(self):
        """Test large_parks filters by minimum acreage."""
        large_park = ParkFactory(size_acres=200)
        small_park = ParkFactory(size_acres=50)

        result = Park.objects.large_parks(min_acres=100)

        assert large_park in result
        assert small_park not in result

    def test__seasonal_parks__excludes_empty_operating_season(self):
        """Test seasonal_parks excludes parks with empty operating_season."""
        seasonal_park = ParkFactory(operating_season="Summer only")
        year_round_park = ParkFactory(operating_season="")

        result = Park.objects.get_queryset().seasonal_parks()

        assert seasonal_park in result
        assert year_round_park not in result

    def test__search_autocomplete__searches_by_name(self):
        """Test search_autocomplete searches park names."""
        park1 = ParkFactory(name="Cedar Point")
        park2 = ParkFactory(name="Kings Island")

        result = list(Park.objects.get_queryset().search_autocomplete(query="Cedar"))

        assert park1 in result
        assert park2 not in result

    def test__search_autocomplete__limits_results(self):
        """Test search_autocomplete respects limit parameter."""
        for i in range(15):
            ParkFactory(name=f"Test Park {i}")

        result = list(Park.objects.get_queryset().search_autocomplete(query="Test", limit=5))

        assert len(result) == 5


@pytest.mark.django_db
class TestParkManager(TestCase):
    """Tests for ParkManager."""

    def test__get_queryset__returns_park_queryset(self):
        """Test get_queryset returns ParkQuerySet."""
        queryset = Park.objects.get_queryset()

        assert isinstance(queryset, ParkQuerySet)

    def test__operating__filters_operating_parks(self):
        """Test operating filters for operating status."""
        operating = ParkFactory(status="OPERATING")
        closed = ParkFactory(status="CLOSED_PERM")

        result = Park.objects.operating()

        assert operating in result
        assert closed not in result

    def test__closed__filters_closed_parks(self):
        """Test closed filters for closed statuses."""
        operating = ParkFactory(status="OPERATING")
        closed_temp = ParkFactory(status="CLOSED_TEMP")
        closed_perm = ParkFactory(status="CLOSED_PERM")

        result = Park.objects.closed()

        assert operating not in result
        assert closed_temp in result
        assert closed_perm in result


@pytest.mark.django_db
class TestParkAreaQuerySet(TestCase):
    """Tests for ParkAreaQuerySet."""

    def test__with_ride_counts__annotates_ride_count(self):
        """Test with_ride_counts adds ride count annotation."""
        park = ParkFactory()
        area = ParkAreaFactory(park=park)
        RideFactory(park=park, park_area=area)
        RideFactory(park=park, park_area=area)
        CoasterFactory(park=park, park_area=area)

        result = ParkArea.objects.with_ride_counts().get(pk=area.pk)

        assert result.ride_count == 3
        assert result.coaster_count == 1

    def test__by_park__filters_by_park_id(self):
        """Test by_park filters areas by park."""
        park1 = ParkFactory()
        park2 = ParkFactory()
        area1 = ParkAreaFactory(park=park1)
        area2 = ParkAreaFactory(park=park2)

        result = ParkArea.objects.by_park(park_id=park1.pk)

        assert area1 in result
        assert area2 not in result

    def test__with_rides__filters_areas_with_rides(self):
        """Test with_rides filters areas that have rides."""
        park = ParkFactory()
        area_with_rides = ParkAreaFactory(park=park)
        area_without_rides = ParkAreaFactory(park=park)
        RideFactory(park=park, park_area=area_with_rides)

        result = ParkArea.objects.get_queryset().with_rides()

        assert area_with_rides in result
        assert area_without_rides not in result


@pytest.mark.django_db
class TestParkReviewQuerySet(TestCase):
    """Tests for ParkReviewQuerySet."""

    def test__for_park__filters_by_park_id(self):
        """Test for_park filters reviews by park."""
        park1 = ParkFactory()
        park2 = ParkFactory()
        user = UserFactory()
        review1 = ParkReviewFactory(park=park1, user=user)
        user2 = UserFactory()
        review2 = ParkReviewFactory(park=park2, user=user2)

        result = ParkReview.objects.for_park(park_id=park1.pk)

        assert review1 in result
        assert review2 not in result

    def test__by_user__filters_by_user_id(self):
        """Test by_user filters reviews by user."""
        user1 = UserFactory()
        user2 = UserFactory()
        review1 = ParkReviewFactory(user=user1)
        review2 = ParkReviewFactory(user=user2)

        result = ParkReview.objects.get_queryset().by_user(user_id=user1.pk)

        assert review1 in result
        assert review2 not in result

    def test__by_rating_range__filters_by_rating(self):
        """Test by_rating_range filters reviews by rating range."""
        user1 = UserFactory()
        user2 = UserFactory()
        user3 = UserFactory()
        high_review = ParkReviewFactory(rating=9, user=user1)
        mid_review = ParkReviewFactory(rating=5, user=user2)
        low_review = ParkReviewFactory(rating=2, user=user3)

        result = ParkReview.objects.by_rating_range(min_rating=7, max_rating=10)

        assert high_review in result
        assert mid_review not in result
        assert low_review not in result

    def test__moderation_required__filters_unpublished_or_unmoderated(self):
        """Test moderation_required filters reviews needing moderation."""
        user1 = UserFactory()
        user2 = UserFactory()
        ParkReviewFactory(is_published=True, user=user1)
        unpublished = ParkReviewFactory(is_published=False, user=user2)

        result = ParkReview.objects.moderation_required()

        # unpublished should definitely be in result
        assert unpublished in result


@pytest.mark.django_db
class TestCompanyQuerySet(TestCase):
    """Tests for CompanyQuerySet."""

    def test__operators__filters_operator_companies(self):
        """Test operators filters for companies with OPERATOR role."""
        operator = OperatorCompanyFactory()
        manufacturer = ManufacturerCompanyFactory()

        result = Company.objects.operators()

        assert operator in result
        assert manufacturer not in result

    def test__manufacturers__filters_manufacturer_companies(self):
        """Test manufacturers filters for companies with MANUFACTURER role."""
        operator = OperatorCompanyFactory()
        manufacturer = ManufacturerCompanyFactory()

        result = Company.objects.manufacturers()

        assert manufacturer in result
        assert operator not in result

    def test__with_park_counts__annotates_park_counts(self):
        """Test with_park_counts adds park count annotations."""
        operator = OperatorCompanyFactory()
        ParkFactory(operator=operator)
        ParkFactory(operator=operator)
        ParkFactory(property_owner=operator)

        result = Company.objects.get_queryset().with_park_counts().get(pk=operator.pk)

        assert result.operated_parks_count == 2
        assert result.owned_parks_count == 1

    def test__major_operators__filters_by_minimum_parks(self):
        """Test major_operators filters by minimum park count."""
        major_operator = OperatorCompanyFactory()
        small_operator = OperatorCompanyFactory()

        for _ in range(6):
            ParkFactory(operator=major_operator)
        for _ in range(2):
            ParkFactory(operator=small_operator)

        result = Company.objects.major_operators(min_parks=5)

        assert major_operator in result
        assert small_operator not in result


@pytest.mark.django_db
class TestCompanyManager(TestCase):
    """Tests for CompanyManager."""

    def test__manufacturers_with_ride_count__annotates_ride_count(self):
        """Test manufacturers_with_ride_count adds ride count annotation."""
        manufacturer = ManufacturerCompanyFactory()
        RideFactory(manufacturer=manufacturer)
        RideFactory(manufacturer=manufacturer)
        RideFactory(manufacturer=manufacturer)

        result = list(Company.objects.manufacturers_with_ride_count())
        mfr = next((c for c in result if c.pk == manufacturer.pk), None)

        assert mfr is not None
        assert mfr.ride_count == 3

    def test__operators_with_park_count__annotates_park_count(self):
        """Test operators_with_park_count adds park count annotation."""
        operator = OperatorCompanyFactory()
        ParkFactory(operator=operator)
        ParkFactory(operator=operator)

        result = list(Company.objects.operators_with_park_count())
        op = next((c for c in result if c.pk == operator.pk), None)

        assert op is not None
        assert op.operated_parks_count == 2
