"""
Comprehensive tests for the parks app state machine.

This module contains tests for:
- Park status FSM transitions
- Park transition wrapper methods
- Transition history logging
- Related model updates during transitions
"""

from datetime import date

from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django_fsm import TransitionNotAllowed

from .models import Company, Park

User = get_user_model()


# ============================================================================
# Park FSM Transition Tests
# ============================================================================


class ParkTransitionTests(TestCase):
    """Comprehensive tests for Park FSM transitions."""

    def setUp(self):
        """Set up test fixtures."""
        self.user = User.objects.create_user(
            username="testuser", email="test@example.com", password="testpass123", role="USER"
        )
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )
        self.admin = User.objects.create_user(
            username="admin", email="admin@example.com", password="testpass123", role="ADMIN"
        )

        # Create operator company
        self.operator = Company.objects.create(
            name="Test Operator", description="Test operator company", roles=["OPERATOR"]
        )

    def _create_park(self, status="OPERATING", **kwargs):
        """Helper to create a Park with specified status."""
        defaults = {
            "name": "Test Park",
            "slug": "test-park",
            "description": "A test park",
            "operator": self.operator,
            "timezone": "America/New_York",
        }
        defaults.update(kwargs)
        return Park.objects.create(status=status, **defaults)

    # -------------------------------------------------------------------------
    # Operating status transitions
    # -------------------------------------------------------------------------

    def test_operating_to_closed_temp_transition(self):
        """Test transition from OPERATING to CLOSED_TEMP."""
        park = self._create_park(status="OPERATING")
        self.assertEqual(park.status, "OPERATING")

        park.transition_to_closed_temp(user=self.user)
        park.save()

        park.refresh_from_db()
        self.assertEqual(park.status, "CLOSED_TEMP")

    def test_operating_to_closed_perm_transition(self):
        """Test transition from OPERATING to CLOSED_PERM."""
        park = self._create_park(status="OPERATING")

        park.transition_to_closed_perm(user=self.moderator)
        park.closing_date = date.today()
        park.save()

        park.refresh_from_db()
        self.assertEqual(park.status, "CLOSED_PERM")
        self.assertIsNotNone(park.closing_date)

    # -------------------------------------------------------------------------
    # Under construction transitions
    # -------------------------------------------------------------------------

    def test_under_construction_to_operating_transition(self):
        """Test transition from UNDER_CONSTRUCTION to OPERATING."""
        park = self._create_park(status="UNDER_CONSTRUCTION")
        self.assertEqual(park.status, "UNDER_CONSTRUCTION")

        park.transition_to_operating(user=self.user)
        park.save()

        park.refresh_from_db()
        self.assertEqual(park.status, "OPERATING")

    # -------------------------------------------------------------------------
    # Closed temp transitions
    # -------------------------------------------------------------------------

    def test_closed_temp_to_operating_transition(self):
        """Test transition from CLOSED_TEMP to OPERATING (reopen)."""
        park = self._create_park(status="CLOSED_TEMP")

        park.transition_to_operating(user=self.user)
        park.save()

        park.refresh_from_db()
        self.assertEqual(park.status, "OPERATING")

    def test_closed_temp_to_closed_perm_transition(self):
        """Test transition from CLOSED_TEMP to CLOSED_PERM."""
        park = self._create_park(status="CLOSED_TEMP")

        park.transition_to_closed_perm(user=self.moderator)
        park.closing_date = date.today()
        park.save()

        park.refresh_from_db()
        self.assertEqual(park.status, "CLOSED_PERM")

    # -------------------------------------------------------------------------
    # Closed perm transitions (to final states)
    # -------------------------------------------------------------------------

    def test_closed_perm_to_demolished_transition(self):
        """Test transition from CLOSED_PERM to DEMOLISHED."""
        park = self._create_park(status="CLOSED_PERM")

        park.transition_to_demolished(user=self.moderator)
        park.save()

        park.refresh_from_db()
        self.assertEqual(park.status, "DEMOLISHED")

    def test_closed_perm_to_relocated_transition(self):
        """Test transition from CLOSED_PERM to RELOCATED."""
        park = self._create_park(status="CLOSED_PERM")

        park.transition_to_relocated(user=self.moderator)
        park.save()

        park.refresh_from_db()
        self.assertEqual(park.status, "RELOCATED")

    # -------------------------------------------------------------------------
    # Invalid transitions (final states)
    # -------------------------------------------------------------------------

    def test_demolished_cannot_transition(self):
        """Test that DEMOLISHED state cannot transition further."""
        park = self._create_park(status="DEMOLISHED")

        with self.assertRaises(TransitionNotAllowed):
            park.transition_to_operating(user=self.moderator)

    def test_relocated_cannot_transition(self):
        """Test that RELOCATED state cannot transition further."""
        park = self._create_park(status="RELOCATED")

        with self.assertRaises(TransitionNotAllowed):
            park.transition_to_operating(user=self.moderator)

    def test_operating_cannot_directly_demolish(self):
        """Test that OPERATING cannot directly transition to DEMOLISHED."""
        park = self._create_park(status="OPERATING")

        with self.assertRaises(TransitionNotAllowed):
            park.transition_to_demolished(user=self.moderator)

    def test_operating_cannot_directly_relocate(self):
        """Test that OPERATING cannot directly transition to RELOCATED."""
        park = self._create_park(status="OPERATING")

        with self.assertRaises(TransitionNotAllowed):
            park.transition_to_relocated(user=self.moderator)

    # -------------------------------------------------------------------------
    # Wrapper method tests
    # -------------------------------------------------------------------------

    def test_reopen_wrapper_method(self):
        """Test the reopen() wrapper method."""
        park = self._create_park(status="CLOSED_TEMP")

        park.reopen(user=self.user)

        park.refresh_from_db()
        self.assertEqual(park.status, "OPERATING")

    def test_close_temporarily_wrapper_method(self):
        """Test the close_temporarily() wrapper method."""
        park = self._create_park(status="OPERATING")

        park.close_temporarily(user=self.user)

        park.refresh_from_db()
        self.assertEqual(park.status, "CLOSED_TEMP")

    def test_close_permanently_wrapper_method(self):
        """Test the close_permanently() wrapper method."""
        park = self._create_park(status="OPERATING")
        closing = date(2025, 12, 31)

        park.close_permanently(closing_date=closing, user=self.moderator)

        park.refresh_from_db()
        self.assertEqual(park.status, "CLOSED_PERM")
        self.assertEqual(park.closing_date, closing)

    def test_close_permanently_without_date(self):
        """Test close_permanently() without closing_date."""
        park = self._create_park(status="OPERATING")

        park.close_permanently(user=self.moderator)

        park.refresh_from_db()
        self.assertEqual(park.status, "CLOSED_PERM")
        self.assertIsNone(park.closing_date)

    def test_demolish_wrapper_method(self):
        """Test the demolish() wrapper method."""
        park = self._create_park(status="CLOSED_PERM")

        park.demolish(user=self.moderator)

        park.refresh_from_db()
        self.assertEqual(park.status, "DEMOLISHED")

    def test_relocate_wrapper_method(self):
        """Test the relocate() wrapper method."""
        park = self._create_park(status="CLOSED_PERM")

        park.relocate(user=self.moderator)

        park.refresh_from_db()
        self.assertEqual(park.status, "RELOCATED")

    def test_start_construction_wrapper_method(self):
        """Test the start_construction() wrapper method if applicable."""
        # This depends on allowed transitions - skip if not allowed
        try:
            park = self._create_park(status="OPERATING")
            park.start_construction(user=self.moderator)
            park.refresh_from_db()
            self.assertEqual(park.status, "UNDER_CONSTRUCTION")
        except TransitionNotAllowed:
            # If transition from OPERATING to UNDER_CONSTRUCTION is not allowed
            pass


# ============================================================================
# Park Transition History Tests
# ============================================================================


class ParkTransitionHistoryTests(TestCase):
    """Tests for Park transition history logging."""

    def setUp(self):
        """Set up test fixtures."""
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )
        self.operator = Company.objects.create(
            name="Test Operator", description="Test operator company", roles=["OPERATOR"]
        )

    def _create_park(self, status="OPERATING"):
        """Helper to create a Park."""
        return Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A test park",
            operator=self.operator,
            status=status,
            timezone="America/New_York",
        )

    def test_transition_creates_state_log(self):
        """Test that transitions create StateLog entries."""
        from django_fsm_log.models import StateLog

        park = self._create_park(status="OPERATING")

        park.transition_to_closed_temp(user=self.moderator)
        park.save()

        park_ct = ContentType.objects.get_for_model(park)
        log = StateLog.objects.filter(content_type=park_ct, object_id=park.id).first()

        self.assertIsNotNone(log)
        self.assertEqual(log.state, "CLOSED_TEMP")
        self.assertEqual(log.by, self.moderator)

    def test_multiple_transitions_create_multiple_logs(self):
        """Test that multiple transitions create multiple log entries."""
        from django_fsm_log.models import StateLog

        park = self._create_park(status="OPERATING")
        park_ct = ContentType.objects.get_for_model(park)

        # First transition
        park.transition_to_closed_temp(user=self.moderator)
        park.save()

        # Second transition
        park.transition_to_operating(user=self.moderator)
        park.save()

        logs = StateLog.objects.filter(content_type=park_ct, object_id=park.id).order_by("timestamp")

        self.assertEqual(logs.count(), 2)
        self.assertEqual(logs[0].state, "CLOSED_TEMP")
        self.assertEqual(logs[1].state, "OPERATING")

    def test_transition_log_includes_user(self):
        """Test that transition logs include the user who made the change."""
        from django_fsm_log.models import StateLog

        park = self._create_park(status="OPERATING")

        park.transition_to_closed_perm(user=self.moderator)
        park.save()

        park_ct = ContentType.objects.get_for_model(park)
        log = StateLog.objects.filter(content_type=park_ct, object_id=park.id).first()

        self.assertEqual(log.by, self.moderator)


# ============================================================================
# Park Model Business Logic Tests
# ============================================================================


class ParkBusinessLogicTests(TestCase):
    """Tests for Park model business logic."""

    def setUp(self):
        """Set up test fixtures."""
        self.operator = Company.objects.create(
            name="Test Operator", description="Test operator company", roles=["OPERATOR"]
        )
        self.property_owner = Company.objects.create(
            name="Property Owner", description="Property owner company", roles=["PROPERTY_OWNER"]
        )

    def test_park_creates_with_valid_operator(self):
        """Test park can be created with valid operator."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A test park",
            operator=self.operator,
            timezone="America/New_York",
        )

        self.assertEqual(park.operator, self.operator)

    def test_park_slug_auto_generated(self):
        """Test that park slug is auto-generated from name."""
        park = Park.objects.create(
            name="My Amazing Theme Park", description="A test park", operator=self.operator, timezone="America/New_York"
        )

        self.assertEqual(park.slug, "my-amazing-theme-park")

    def test_park_url_generated(self):
        """Test that frontend URL is generated on save."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A test park",
            operator=self.operator,
            timezone="America/New_York",
        )

        self.assertIn("test-park", park.url)

    def test_opening_year_computed_from_opening_date(self):
        """Test that opening_year is computed from opening_date."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A test park",
            operator=self.operator,
            opening_date=date(2020, 6, 15),
            timezone="America/New_York",
        )

        self.assertEqual(park.opening_year, 2020)

    def test_search_text_populated(self):
        """Test that search_text is populated on save."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A wonderful theme park",
            operator=self.operator,
            timezone="America/New_York",
        )

        self.assertIn("test park", park.search_text)
        self.assertIn("wonderful theme park", park.search_text)
        self.assertIn("test operator", park.search_text)

    def test_park_with_property_owner(self):
        """Test park with separate property owner."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A test park",
            operator=self.operator,
            property_owner=self.property_owner,
            timezone="America/New_York",
        )

        self.assertEqual(park.operator, self.operator)
        self.assertEqual(park.property_owner, self.property_owner)


# ============================================================================
# Park Historical Slug Tests
# ============================================================================


class ParkSlugHistoryTests(TestCase):
    """Tests for Park historical slug tracking."""

    def setUp(self):
        """Set up test fixtures."""
        self.operator = Company.objects.create(
            name="Test Operator", description="Test operator company", roles=["OPERATOR"]
        )

    def test_historical_slug_created_on_name_change(self):
        """Test that historical slug is created when name changes."""
        from django.contrib.contenttypes.models import ContentType

        from apps.core.history import HistoricalSlug

        park = Park.objects.create(
            name="Original Name", description="A test park", operator=self.operator, timezone="America/New_York"
        )

        original_slug = park.slug

        # Change name
        park.name = "New Name"
        park.save()

        # Check historical slug was created
        park_ct = ContentType.objects.get_for_model(park)
        historical = HistoricalSlug.objects.filter(content_type=park_ct, object_id=park.id, slug=original_slug).first()

        self.assertIsNotNone(historical)
        self.assertEqual(historical.slug, original_slug)

    def test_get_by_slug_finds_current_slug(self):
        """Test get_by_slug finds park by current slug."""
        park = Park.objects.create(
            name="Test Park",
            slug="test-park",
            description="A test park",
            operator=self.operator,
            timezone="America/New_York",
        )

        found_park, is_historical = Park.get_by_slug("test-park")

        self.assertEqual(found_park, park)
        self.assertFalse(is_historical)

    def test_get_by_slug_finds_historical_slug(self):
        """Test get_by_slug finds park by historical slug."""

        park = Park.objects.create(
            name="Original Name", description="A test park", operator=self.operator, timezone="America/New_York"
        )

        original_slug = park.slug

        # Change name to create historical slug
        park.name = "New Name"
        park.save()

        # Find by historical slug
        found_park, is_historical = Park.get_by_slug(original_slug)

        self.assertEqual(found_park, park)
        self.assertTrue(is_historical)
