"""
Tests for query optimization patterns in the parks app.

These tests verify that:
1. Manager methods use proper select_related/prefetch_related
2. Views don't trigger N+1 queries
3. Computed fields are updated correctly
"""

from django.db import connection
from django.test import TestCase
from django.test.utils import CaptureQueriesContext

from apps.parks.models import Company, Park, ParkLocation


class ParkQueryOptimizationTests(TestCase):
    """Tests for Park query optimization."""

    @classmethod
    def setUpTestData(cls):
        """Set up test data for all tests."""
        # Create a test operator company
        cls.operator = Company.objects.create(
            name="Test Operator",
            slug="test-operator",
            roles=["OPERATOR"],
        )

        # Create test parks
        cls.parks = []
        for i in range(5):
            park = Park.objects.create(
                name=f"Test Park {i}",
                slug=f"test-park-{i}",
                operator=cls.operator,
                timezone="UTC",
            )
            # Create location for each park
            ParkLocation.objects.create(
                park=park,
                city=f"City {i}",
                state="CA",
                country="USA",
            )
            cls.parks.append(park)

    def test_optimized_for_list_query_count(self):
        """Verify optimized_for_list uses expected number of queries."""
        with CaptureQueriesContext(connection) as context:
            parks = Park.objects.optimized_for_list()
            # Force evaluation
            list(parks)

        # Should be a small number of queries (main query + prefetch)
        # The exact count depends on prefetch_related configuration
        self.assertLessEqual(
            len(context.captured_queries), 5, f"Expected <= 5 queries, got {len(context.captured_queries)}"
        )

    def test_optimized_for_detail_query_count(self):
        """Verify optimized_for_detail uses expected number of queries."""
        with CaptureQueriesContext(connection) as context:
            parks = Park.objects.optimized_for_detail()
            park = parks.first()
            if park:
                # Access related objects that should be prefetched
                _ = park.operator
                _ = list(park.areas.all())

        # Should be a reasonable number of queries
        self.assertLessEqual(
            len(context.captured_queries), 10, f"Expected <= 10 queries, got {len(context.captured_queries)}"
        )

    def test_with_location_includes_location(self):
        """Verify with_location prefetches location data."""
        with CaptureQueriesContext(connection) as context:
            parks = Park.objects.with_location()
            for park in parks:
                # Accessing location should not cause additional queries
                _ = park.location

        # Should be minimal queries
        self.assertLessEqual(len(context.captured_queries), 3)

    def test_for_map_display_returns_minimal_fields(self):
        """Verify for_map_display returns only necessary fields."""
        result = Park.objects.for_map_display()
        if result.exists():
            first = result.first()
            # Should include these fields
            self.assertIn("id", first)
            self.assertIn("name", first)
            self.assertIn("slug", first)
            self.assertIn("status", first)

    def test_search_autocomplete_limits_results(self):
        """Verify search_autocomplete respects limit parameter."""
        result = Park.objects.search_autocomplete(query="Test", limit=3)
        self.assertLessEqual(len(result), 3)


class CompanyQueryOptimizationTests(TestCase):
    """Tests for Company query optimization."""

    @classmethod
    def setUpTestData(cls):
        """Set up test data for all tests."""
        # Create test companies with different roles
        cls.manufacturers = []
        for i in range(5):
            company = Company.objects.create(
                name=f"Manufacturer {i}",
                slug=f"manufacturer-{i}",
                roles=["MANUFACTURER"],
            )
            cls.manufacturers.append(company)

        cls.operators = []
        for i in range(3):
            company = Company.objects.create(
                name=f"Operator {i}",
                slug=f"operator-{i}",
                roles=["OPERATOR"],
            )
            cls.operators.append(company)

    def test_manufacturers_query_only_returns_manufacturers(self):
        """Verify manufacturers() only returns companies with MANUFACTURER role."""
        result = Company.objects.manufacturers()
        for company in result:
            self.assertIn("MANUFACTURER", company.roles)

    def test_operators_query_only_returns_operators(self):
        """Verify operators() only returns companies with OPERATOR role."""
        result = Company.objects.operators()
        for company in result:
            self.assertIn("OPERATOR", company.roles)

    def test_manufacturers_with_ride_count_includes_annotation(self):
        """Verify manufacturers_with_ride_count adds ride_count annotation."""
        result = Company.objects.manufacturers_with_ride_count()
        if result.exists():
            first = result.first()
            # Should have ride_count attribute
            self.assertTrue(hasattr(first, "ride_count"))

    def test_operators_with_park_count_includes_annotation(self):
        """Verify operators_with_park_count adds park count annotations."""
        result = Company.objects.operators_with_park_count()
        if result.exists():
            first = result.first()
            # Should have operated_parks_count attribute
            self.assertTrue(hasattr(first, "operated_parks_count"))


class ComputedFieldMaintenanceTests(TestCase):
    """Tests for computed field maintenance via signals."""

    @classmethod
    def setUpTestData(cls):
        """Set up test data for all tests."""
        cls.operator = Company.objects.create(
            name="Test Operator",
            slug="test-operator",
            roles=["OPERATOR"],
        )

    def test_park_search_text_includes_name(self):
        """Verify park search_text includes park name."""
        park = Park.objects.create(
            name="Magic Kingdom",
            slug="magic-kingdom",
            operator=self.operator,
            timezone="UTC",
        )
        self.assertIn("magic kingdom", park.search_text.lower())

    def test_park_search_text_includes_description(self):
        """Verify park search_text includes description."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A magical theme park experience",
            operator=self.operator,
            timezone="UTC",
        )
        self.assertIn("magical", park.search_text.lower())

    def test_park_search_text_includes_operator(self):
        """Verify park search_text includes operator name."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park-2",
            operator=self.operator,
            timezone="UTC",
        )
        self.assertIn("test operator", park.search_text.lower())

    def test_park_opening_year_computed_from_date(self):
        """Verify opening_year is computed from opening_date."""
        from datetime import date

        park = Park.objects.create(
            name="Test Park",
            slug="test-park-3",
            operator=self.operator,
            timezone="UTC",
            opening_date=date(1971, 10, 1),
        )
        self.assertEqual(park.opening_year, 1971)

    def test_park_search_text_updated_on_location_change(self):
        """Verify park search_text updates when location changes."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park-4",
            operator=self.operator,
            timezone="UTC",
        )

        # Initially no location in search_text

        # Add location
        ParkLocation.objects.create(
            park=park,
            city="Orlando",
            state="Florida",
            country="USA",
        )

        # Refresh park from database
        park.refresh_from_db()

        # Search text should now include location
        # Note: This depends on signal handlers being properly registered
        # The actual behavior may vary based on signal configuration
