# Example: Prowler API Test Patterns
# Source: api/src/backend/api/tests/test_views.py

from unittest.mock import Mock, patch

import pytest
from conftest import (
    API_JSON_CONTENT_TYPE,
    TEST_PASSWORD,
    TEST_USER,
    get_api_tokens,
    get_authorization_header,
)
from django.urls import reverse
from rest_framework import status

from api.models import Provider, Scan, StateChoices
from api.rls import Tenant


@pytest.mark.django_db
class TestProviderViewSet:
    """Example API tests for Provider endpoints."""

    def test_list_providers(self, authenticated_client, aws_provider):
        """GET list returns all providers for authenticated tenant."""
        response = authenticated_client.get(reverse("provider-list"))

        assert response.status_code == status.HTTP_200_OK
        assert len(response.json()["data"]) == 1

    def test_create_provider(self, authenticated_client):
        """POST with JSON:API format creates provider."""
        response = authenticated_client.post(
            reverse("provider-list"),
            data={
                "data": {
                    "type": "providers",
                    "attributes": {
                        "provider": "aws",
                        "uid": "123456789012",
                        "alias": "my-aws-account",
                    },
                }
            },
            format="vnd.api+json",  # Use format= for POST
        )

        assert response.status_code == status.HTTP_201_CREATED
        assert response.json()["data"]["attributes"]["uid"] == "123456789012"

    def test_update_provider(self, authenticated_client, aws_provider):
        """PATCH with JSON:API format updates provider."""
        provider = aws_provider

        payload = {
            "data": {
                "type": "providers",
                "id": str(provider.id),  # ID required for PATCH
                "attributes": {"alias": "updated-alias"},
            }
        }

        response = authenticated_client.patch(
            reverse("provider-detail", kwargs={"pk": provider.id}),
            data=payload,
            content_type="application/vnd.api+json",  # Use content_type= for PATCH
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json()["data"]["attributes"]["alias"] == "updated-alias"


@pytest.mark.django_db
class TestRLSIsolation:
    """Example RLS cross-tenant isolation tests."""

    def test_cross_tenant_access_returns_404(
        self, authenticated_client, tenants_fixture
    ):
        """User cannot see resources from other tenants - returns 404 NOT 403."""
        # Create resource in tenant user has NO access to (tenant[2] is isolated)
        other_tenant = tenants_fixture[2]
        foreign_provider = Provider.objects.create(
            provider="aws",
            uid="999888777666",
            alias="foreign_provider",
            tenant_id=other_tenant.id,
        )

        # Try to access - should get 404 (not 403!)
        response = authenticated_client.get(
            reverse("provider-detail", args=[foreign_provider.id])
        )
        assert response.status_code == status.HTTP_404_NOT_FOUND

    def test_list_excludes_other_tenants(
        self, authenticated_client, aws_provider, tenants_fixture
    ):
        """List endpoints only return resources from user's tenants."""
        # Create provider in isolated tenant
        other_tenant = tenants_fixture[2]
        Provider.objects.create(
            provider="aws",
            uid="foreign123",
            tenant_id=other_tenant.id,
        )

        response = authenticated_client.get(reverse("provider-list"))
        assert response.status_code == status.HTTP_200_OK

        # Should only see the AWS provider in tenant[0]
        assert len(response.json()["data"]) == 1


@pytest.mark.django_db
class TestRBACPermissions:
    """Example RBAC permission tests."""

    def test_requires_permission(self, authenticated_client_no_permissions_rbac):
        """Users without manage_providers cannot create providers."""
        response = authenticated_client_no_permissions_rbac.post(
            reverse("provider-list"),
            data={
                "data": {
                    "type": "providers",
                    "attributes": {"provider": "aws", "uid": "123456789012"},
                }
            },
            format="vnd.api+json",
        )
        assert response.status_code == status.HTTP_403_FORBIDDEN

    def test_user_with_no_roles_denied(self, authenticated_client_rbac_noroles):
        """User with membership but no roles gets 403."""
        response = authenticated_client_rbac_noroles.get(reverse("user-list"))
        assert response.status_code == status.HTTP_403_FORBIDDEN

    def test_admin_sees_all(self, authenticated_client_rbac, aws_provider):
        """Admin with unlimited_visibility=True sees all providers."""
        response = authenticated_client_rbac.get(reverse("provider-list"))
        assert response.status_code == status.HTTP_200_OK


@pytest.mark.django_db
class TestAsyncOperations:
    """Example async task tests - mock BOTH .delay() AND Task.objects.get."""

    @patch("api.v1.views.Task.objects.get")
    @patch("api.v1.views.delete_provider_task.delay")
    def test_delete_provider_returns_202(
        self,
        mock_delete_task,
        mock_task_get,
        authenticated_client,
        aws_provider,
        tasks_fixture,
    ):
        """DELETE returns 202 Accepted with Content-Location header."""
        provider = aws_provider
        prowler_task = tasks_fixture[0]

        # Mock the Celery task
        task_mock = Mock()
        task_mock.id = prowler_task.id
        mock_delete_task.return_value = task_mock
        mock_task_get.return_value = prowler_task

        response = authenticated_client.delete(
            reverse("provider-detail", kwargs={"pk": provider.id})
        )

        assert response.status_code == status.HTTP_202_ACCEPTED
        assert "Content-Location" in response.headers
        assert f"/api/v1/tasks/{prowler_task.id}" in response.headers["Content-Location"]

        # Verify task was called
        mock_delete_task.assert_called_once()

    @patch("api.v1.views.Task.objects.get")
    @patch("api.v1.views.perform_scan_task.delay")
    def test_trigger_scan_returns_202(
        self,
        mock_scan_task,
        mock_task_get,
        authenticated_client,
        aws_provider,
        tasks_fixture,
    ):
        """POST to scan trigger returns 202 with task location."""
        provider = aws_provider
        prowler_task = tasks_fixture[0]

        task_mock = Mock()
        task_mock.id = prowler_task.id
        mock_scan_task.return_value = task_mock
        mock_task_get.return_value = prowler_task

        response = authenticated_client.post(
            reverse("provider-scan", kwargs={"pk": provider.id}),
            format="vnd.api+json",
        )

        assert response.status_code == status.HTTP_202_ACCEPTED


@pytest.mark.django_db
class TestJSONAPIResponses:
    """Example JSON:API response handling."""

    def test_read_single_resource(self, authenticated_client, aws_provider):
        """Read data from single resource response."""
        provider = aws_provider
        response = authenticated_client.get(
            reverse("provider-detail", kwargs={"pk": provider.id})
        )

        data = response.json()["data"]
        attrs = data["attributes"]
        resource_id = data["id"]

        assert resource_id == str(provider.id)
        assert attrs["provider"] == provider.provider

    def test_read_list_response(self, authenticated_client, aws_provider):
        """Read data from list response."""
        response = authenticated_client.get(reverse("provider-list"))

        items = response.json()["data"]
        assert len(items) == 1

    def test_read_relationships(self, authenticated_client, scans_fixture):
        """Read relationship data."""
        scan = scans_fixture[0]
        response = authenticated_client.get(
            reverse("scan-detail", kwargs={"pk": scan.id})
        )

        data = response.json()["data"]
        relationships = data["relationships"]
        provider_rel = relationships["provider"]["data"]

        assert provider_rel["type"] == "providers"
        assert provider_rel["id"] == str(scan.provider_id)

    def test_error_response(self, authenticated_client):
        """Read error response structure."""
        response = authenticated_client.post(
            reverse("user-list"),
            data={"email": "invalid"},  # Missing required fields
            format="json",
        )

        assert response.status_code == status.HTTP_400_BAD_REQUEST
        errors = response.json()["errors"]
        # Error has source.pointer and detail
        assert "source" in errors[0]
        assert "detail" in errors[0]


@pytest.mark.django_db
class TestSoftDelete:
    """Example soft-delete manager tests."""

    def test_objects_excludes_soft_deleted(self, aws_provider):
        """Default manager excludes soft-deleted records."""
        provider = aws_provider
        provider.is_deleted = True
        provider.save()

        # objects manager excludes deleted
        assert provider not in Provider.objects.all()

        # all_objects includes deleted
        assert provider in Provider.all_objects.all()


# =============================================================================
# CELERY TASK TESTING
# =============================================================================


@pytest.mark.django_db
class TestCeleryTaskLogic:
    """Example: Testing Celery task logic directly with apply()."""

    def test_task_logic_directly(self, tenants_fixture, aws_provider):
        """Use apply() for synchronous execution without Celery worker."""
        from tasks.tasks import check_provider_connection_task

        tenant = tenants_fixture[0]
        provider = aws_provider

        # Execute task synchronously (no broker needed)
        result = check_provider_connection_task.apply(
            kwargs={"tenant_id": str(tenant.id), "provider_id": str(provider.id)}
        )

        assert result.successful()
        assert result.result["connected"] is True


@pytest.mark.django_db
class TestCeleryCanvas:
    """Example: Testing Canvas (chain/group) task orchestration."""

    @patch("tasks.tasks.chain")
    @patch("tasks.tasks.group")
    def test_post_scan_workflow(self, mock_group, mock_chain, tenants_fixture):
        """Mock chain/group to verify task orchestration."""
        from tasks.tasks import _perform_scan_complete_tasks

        tenant = tenants_fixture[0]

        # Mock chain.apply_async
        mock_chain_instance = Mock()
        mock_chain.return_value = mock_chain_instance

        _perform_scan_complete_tasks(str(tenant.id), "scan-123", "provider-456")

        # Verify chain was called
        assert mock_chain.called
        mock_chain_instance.apply_async.assert_called()


@pytest.mark.django_db
class TestSetTenantDecorator:
    """Example: Testing @set_tenant decorator behavior."""

    @patch("api.decorators.connection")
    def test_sets_rls_context(self, mock_conn, tenants_fixture, aws_provider):
        """Verify @set_tenant sets RLS context via SET_CONFIG_QUERY."""
        from tasks.tasks import check_provider_connection_task

        tenant = tenants_fixture[0]
        provider = aws_provider

        # Call task with tenant_id - decorator sets RLS and pops it
        check_provider_connection_task.apply(
            kwargs={"tenant_id": str(tenant.id), "provider_id": str(provider.id)}
        )

        # Verify SET_CONFIG_QUERY was executed
        mock_conn.cursor.return_value.__enter__.return_value.execute.assert_called()


@pytest.mark.django_db
class TestBeatScheduling:
    """Example: Testing Beat scheduled task creation."""

    @patch("tasks.beat.perform_scheduled_scan_task.apply_async")
    def test_schedule_provider_scan(self, mock_apply, aws_provider):
        """Verify periodic task is created with correct settings."""
        from django_celery_beat.models import PeriodicTask

        from tasks.beat import schedule_provider_scan

        provider = aws_provider
        mock_apply.return_value = Mock(id="task-123")

        schedule_provider_scan(provider)

        # Verify periodic task created
        assert PeriodicTask.objects.filter(
            name=f"scan-perform-scheduled-{provider.id}"
        ).exists()

        # Verify immediate execution with countdown
        mock_apply.assert_called_once()
        call_kwargs = mock_apply.call_args
        assert call_kwargs.kwargs.get("countdown") == 5
