"""
E2E Tests for FSM Permission Guards

Tests that unauthorized users cannot execute FSM transitions:
- Unauthenticated users cannot see transition buttons
- Regular users cannot approve submissions
- Regular users cannot change park/ride status
- Moderators can approve but not admin-only transitions
- Transition buttons hidden when not allowed

These tests verify:
- Transition buttons are NOT visible for unauthorized users
- Direct POST requests return 403 Forbidden
- Database state does NOT change after failed transition attempt
- Error toast displays "Permission denied" message
"""

import pytest
from playwright.sync_api import Page, expect


class TestUnauthenticatedUserPermissions:
    """Tests for unauthenticated user permission guards."""

    def test_unauthenticated_user_cannot_see_moderation_dashboard(self, page: Page, live_server):
        """Test that unauthenticated users are redirected from moderation dashboard."""
        # Navigate to moderation dashboard without logging in
        response = page.goto(f"{live_server.url}/moderation/dashboard/")

        # Should be redirected to login page or see access denied
        # Check URL contains login or access denied
        current_url = page.url
        assert "login" in current_url or "denied" in current_url or response.status == 403

    def test_unauthenticated_user_cannot_see_transition_buttons(self, page: Page, live_server, db):
        """Test that unauthenticated users cannot see transition buttons on park detail."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="OPERATING").first()
        if not park:
            pytest.skip("No operating park available")

        page.goto(f"{live_server.url}/parks/{park.slug}/")
        page.wait_for_load_state("networkidle")

        # Status action buttons should NOT be visible
        status_actions = page.locator("[data-park-status-actions]")

        # Either the section doesn't exist or the buttons are not there
        if status_actions.is_visible():
            close_temp_btn = status_actions.get_by_role("button", name="Close Temporarily")
            expect(close_temp_btn).not_to_be_visible()

    def test_unauthenticated_direct_post_returns_403(self, page: Page, live_server, db):
        """Test that direct POST to FSM endpoint returns 403 for unauthenticated user."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="OPERATING").first()
        if not park:
            pytest.skip("No operating park available")

        # Attempt to POST directly to FSM transition endpoint
        response = page.request.post(
            f"{live_server.url}/core/fsm/parks/park/{park.pk}/transition/transition_to_closed_temp/",
            headers={"HX-Request": "true"},
        )

        # Should get 403 Forbidden
        assert response.status == 403 or response.status == 302  # 302 redirect to login

        # Verify database state did NOT change
        park.refresh_from_db()
        assert park.status == "OPERATING"


class TestRegularUserPermissions:
    """Tests for regular (non-moderator) user permission guards."""

    def test_regular_user_cannot_approve_submission(self, auth_page: Page, live_server, db):
        """Test that regular users cannot approve submissions."""
        from django.contrib.auth import get_user_model
        from django.contrib.contenttypes.models import ContentType

        from apps.moderation.models import EditSubmission
        from apps.parks.models import Park

        User = get_user_model()

        # Create a pending submission
        user = User.objects.filter(username="testuser").first()
        if not user:
            pytest.skip("Test user not found")

        park = Park.objects.first()
        if not park:
            pytest.skip("No park available")

        content_type = ContentType.objects.get_for_model(Park)

        submission = EditSubmission.objects.create(
            user=user,
            content_type=content_type,
            object_id=park.pk,
            submission_type="EDIT",
            changes={"description": "Test change"},
            reason="Permission test",
            status="PENDING",
        )

        try:
            # Navigate to moderation dashboard as regular user
            auth_page.goto(f"{live_server.url}/moderation/dashboard/")

            # Regular user should be redirected or denied
            current_url = auth_page.url

            # If somehow on dashboard, verify no approve button
            if "dashboard" in current_url:
                submission_row = auth_page.locator(f'[data-submission-id="{submission.pk}"]')
                if submission_row.is_visible():
                    approve_btn = submission_row.get_by_role("button", name="Approve")
                    expect(approve_btn).not_to_be_visible()

            # Try direct POST - should be denied
            response = auth_page.request.post(
                f"{live_server.url}/core/fsm/moderation/editsubmission/{submission.pk}/transition/transition_to_approved/",
                headers={"HX-Request": "true"},
            )

            # Should be denied (403 or 302 redirect)
            assert response.status in [302, 403]

            # Verify database state did NOT change
            submission.refresh_from_db()
            assert submission.status == "PENDING"

        finally:
            submission.delete()

    def test_regular_user_cannot_change_park_status(self, auth_page: Page, live_server, db):
        """Test that regular users cannot change park status."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="OPERATING").first()
        if not park:
            pytest.skip("No operating park available")

        auth_page.goto(f"{live_server.url}/parks/{park.slug}/")
        auth_page.wait_for_load_state("networkidle")

        # Status action buttons should NOT be visible to regular user
        status_actions = auth_page.locator("[data-park-status-actions]")

        if status_actions.is_visible():
            close_temp_btn = status_actions.get_by_role("button", name="Close Temporarily")
            expect(close_temp_btn).not_to_be_visible()

        # Try direct POST - should be denied
        response = auth_page.request.post(
            f"{live_server.url}/core/fsm/parks/park/{park.pk}/transition/transition_to_closed_temp/",
            headers={"HX-Request": "true"},
        )

        # Should be denied
        assert response.status in [302, 400, 403]

        # Verify database state did NOT change
        park.refresh_from_db()
        assert park.status == "OPERATING"

    def test_regular_user_cannot_change_ride_status(self, auth_page: Page, live_server, db):
        """Test that regular users cannot change ride status."""
        from apps.rides.models import Ride

        ride = Ride.objects.filter(status="OPERATING").first()
        if not ride:
            pytest.skip("No operating ride available")

        auth_page.goto(f"{live_server.url}/parks/{ride.park.slug}/rides/{ride.slug}/")
        auth_page.wait_for_load_state("networkidle")

        # Status action buttons should NOT be visible to regular user
        status_actions = auth_page.locator("[data-ride-status-actions]")

        if status_actions.is_visible():
            close_temp_btn = status_actions.get_by_role("button", name="Close Temporarily")
            expect(close_temp_btn).not_to_be_visible()

        # Try direct POST - should be denied
        response = auth_page.request.post(
            f"{live_server.url}/core/fsm/rides/ride/{ride.pk}/transition/transition_to_closed_temp/",
            headers={"HX-Request": "true"},
        )

        # Should be denied
        assert response.status in [302, 400, 403]

        # Verify database state did NOT change
        ride.refresh_from_db()
        assert ride.status == "OPERATING"


class TestModeratorPermissions:
    """Tests for moderator-specific permission guards."""

    def test_moderator_can_approve_submission(self, mod_page: Page, live_server, db):
        """Test that moderators CAN see and use approve button."""
        from django.contrib.auth import get_user_model
        from django.contrib.contenttypes.models import ContentType

        from apps.moderation.models import EditSubmission
        from apps.parks.models import Park

        User = get_user_model()

        # Create a pending submission
        user = User.objects.filter(username="testuser").first()
        if not user:
            user = User.objects.create_user(username="testuser", email="testuser@example.com", password="testpass123")

        park = Park.objects.first()
        if not park:
            pytest.skip("No park available")

        content_type = ContentType.objects.get_for_model(Park)

        submission = EditSubmission.objects.create(
            user=user,
            content_type=content_type,
            object_id=park.pk,
            submission_type="EDIT",
            changes={"description": "Test change for moderator"},
            reason="Moderator permission test",
            status="PENDING",
        )

        try:
            mod_page.goto(f"{live_server.url}/moderation/dashboard/")
            mod_page.wait_for_load_state("networkidle")

            # Moderator should be able to see the submission
            submission_row = mod_page.locator(f'[data-submission-id="{submission.pk}"]')

            if submission_row.is_visible():
                # Should see approve button
                approve_btn = submission_row.get_by_role("button", name="Approve")
                expect(approve_btn).to_be_visible()

        finally:
            submission.delete()

    def test_moderator_can_change_park_status(self, mod_page: Page, live_server, db):
        """Test that moderators CAN see and use park status change buttons."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="OPERATING").first()
        if not park:
            pytest.skip("No operating park available")

        mod_page.goto(f"{live_server.url}/parks/{park.slug}/")
        mod_page.wait_for_load_state("networkidle")

        # Status action buttons SHOULD be visible to moderator
        status_actions = mod_page.locator("[data-park-status-actions]")

        if status_actions.is_visible():
            # Should see close temporarily button
            close_temp_btn = status_actions.get_by_role("button", name="Close Temporarily")
            expect(close_temp_btn).to_be_visible()

    def test_moderator_cannot_access_admin_only_transitions(self, mod_page: Page, live_server, db):
        """Test that moderators CANNOT access admin-only transitions."""
        # This test verifies that certain transitions require admin privileges
        # Specific transitions depend on the FSM configuration

        from apps.parks.models import Park

        # Get a permanently closed park for testing admin-only demolish
        park = Park.objects.filter(status="CLOSED_PERM").first()
        if not park:
            # Create one
            park = Park.objects.filter(status="OPERATING").first()
            if park:
                park.status = "CLOSED_PERM"
                park.save()
            else:
                pytest.skip("No park available for testing")

        mod_page.goto(f"{live_server.url}/parks/{park.slug}/")
        mod_page.wait_for_load_state("networkidle")

        # Check for admin-only buttons (if any are configured)
        # The specific buttons that should be hidden depend on the FSM configuration
        status_actions = mod_page.locator("[data-park-status-actions]")

        # If there are admin-only transitions, verify they're hidden
        # This is a placeholder - actual admin-only transitions depend on configuration
        admin_only_btn = status_actions.get_by_role("button", name="Force Delete")  # Example admin-only action
        expect(admin_only_btn).not_to_be_visible()


class TestPermissionDeniedErrorHandling:
    """Tests for error handling when permission is denied."""

    def test_permission_denied_shows_error_toast(self, auth_page: Page, live_server, db):
        """Test that permission denied errors show appropriate toast."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="OPERATING").first()
        if not park:
            pytest.skip("No operating park available")

        # Navigate to the page first
        auth_page.goto(f"{live_server.url}/parks/{park.slug}/")
        auth_page.wait_for_load_state("networkidle")

        # Make the request programmatically with HTMX header
        response = auth_page.evaluate(
            """
            async () => {
                const response = await fetch('/core/fsm/parks/park/"""
            + str(park.pk)
            + """/transition/transition_to_closed_temp/', {
                    method: 'POST',
                    headers: {
                        'HX-Request': 'true',
                        'Content-Type': 'application/x-www-form-urlencoded'
                    },
                    credentials: 'include'
                });
                return {
                    status: response.status,
                    hxTrigger: response.headers.get('HX-Trigger')
                };
            }
        """
        )

        # Check if error toast was triggered
        if response and response.get("status") in [400, 403]:
            hx_trigger = response.get("hxTrigger")
            if hx_trigger:
                assert "showToast" in hx_trigger
                assert "error" in hx_trigger.lower() or "denied" in hx_trigger.lower()

    def test_database_state_unchanged_on_permission_denied(self, auth_page: Page, live_server, db):
        """Test that database state is unchanged when permission is denied."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="OPERATING").first()
        if not park:
            pytest.skip("No operating park available")

        original_status = park.status

        # Attempt unauthorized transition via direct fetch
        auth_page.goto(f"{live_server.url}/parks/{park.slug}/")
        auth_page.wait_for_load_state("networkidle")

        auth_page.evaluate(
            """
            async () => {
                await fetch('/core/fsm/parks/park/"""
            + str(park.pk)
            + """/transition/transition_to_closed_temp/', {
                    method: 'POST',
                    headers: {
                        'HX-Request': 'true',
                        'Content-Type': 'application/x-www-form-urlencoded'
                    },
                    credentials: 'include'
                });
            }
        """
        )

        # Verify database state did NOT change
        park.refresh_from_db()
        assert park.status == original_status


class TestTransitionButtonVisibility:
    """Tests for correct transition button visibility based on permissions and state."""

    def test_transition_button_hidden_when_state_invalid(self, mod_page: Page, live_server, db):
        """Test that transition buttons are hidden when the current state is invalid."""
        from apps.parks.models import Park

        # Get an operating park
        park = Park.objects.filter(status="OPERATING").first()
        if not park:
            pytest.skip("No operating park available")

        mod_page.goto(f"{live_server.url}/parks/{park.slug}/")
        mod_page.wait_for_load_state("networkidle")

        status_actions = mod_page.locator("[data-park-status-actions]")

        # Reopen button should NOT be visible for operating park
        # (can't reopen something that's already operating)
        reopen_btn = status_actions.get_by_role("button", name="Reopen")
        expect(reopen_btn).not_to_be_visible()

        # Demolish should NOT be visible for operating park
        # (can only demolish from CLOSED_PERM)
        demolish_btn = status_actions.get_by_role("button", name="Mark as Demolished")
        expect(demolish_btn).not_to_be_visible()

    def test_correct_buttons_shown_for_closed_temp_state(self, mod_page: Page, live_server, db):
        """Test that correct buttons are shown for temporarily closed state."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="CLOSED_TEMP").first()
        if not park:
            # Create one
            park = Park.objects.filter(status="OPERATING").first()
            if park:
                park.status = "CLOSED_TEMP"
                park.save()
            else:
                pytest.skip("No park available for testing")

        mod_page.goto(f"{live_server.url}/parks/{park.slug}/")
        mod_page.wait_for_load_state("networkidle")

        status_actions = mod_page.locator("[data-park-status-actions]")

        # Reopen button SHOULD be visible
        reopen_btn = status_actions.get_by_role("button", name="Reopen")
        expect(reopen_btn).to_be_visible()

        # Close Temporarily should NOT be visible (already closed)
        close_temp_btn = status_actions.get_by_role("button", name="Close Temporarily")
        expect(close_temp_btn).not_to_be_visible()

    def test_correct_buttons_shown_for_closed_perm_state(self, mod_page: Page, live_server, db):
        """Test that correct buttons are shown for permanently closed state."""
        from apps.parks.models import Park

        park = Park.objects.filter(status="CLOSED_PERM").first()
        if not park:
            # Create one
            park = Park.objects.filter(status="OPERATING").first()
            if park:
                park.status = "CLOSED_PERM"
                park.save()
            else:
                pytest.skip("No park available for testing")

        mod_page.goto(f"{live_server.url}/parks/{park.slug}/")
        mod_page.wait_for_load_state("networkidle")

        status_actions = mod_page.locator("[data-park-status-actions]")

        # Demolish/Relocate buttons SHOULD be visible
        demolish_btn = status_actions.get_by_role("button", name="Mark as Demolished")
        relocate_btn = status_actions.get_by_role("button", name="Mark as Relocated")

        # At least one of these should be visible for CLOSED_PERM
        visible = demolish_btn.is_visible() or relocate_btn.is_visible()
        assert visible, "Expected demolish or relocate button for CLOSED_PERM state"

        # Reopen should still be visible to restore to operating
        status_actions.get_by_role("button", name="Reopen")
        # May or may not be visible depending on FSM configuration
