# tests/api/routes/syndarix/test_sprints.py
"""
Comprehensive tests for the Sprints API endpoints.

Tests cover:
- CRUD operations (create, read, update, delete)
- Lifecycle operations (start, complete, cancel)
- Issue management (add/remove issues)
- Velocity metrics
- Authorization (project ownership, superuser access)
- Error handling
"""

import uuid
from datetime import date, timedelta

import pytest
import pytest_asyncio
from fastapi import status


@pytest_asyncio.fixture
async def test_project(client, user_token):
    """Create a test project for sprint tests."""
    response = await client.post(
        "/api/v1/projects",
        json={"name": "Sprint Test Project", "slug": "sprint-test-project"},
        headers={"Authorization": f"Bearer {user_token}"},
    )
    return response.json()


@pytest_asyncio.fixture
async def superuser_project(client, superuser_token):
    """Create a project owned by superuser."""
    response = await client.post(
        "/api/v1/projects",
        json={"name": "Superuser Project", "slug": "superuser-project"},
        headers={"Authorization": f"Bearer {superuser_token}"},
    )
    return response.json()


@pytest.mark.asyncio
class TestCreateSprint:
    """Tests for POST /api/v1/projects/{project_id}/sprints endpoint."""

    async def test_create_sprint_success(self, client, user_token, test_project):
        """Test successful sprint creation."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint 1",
                "number": 1,
                "goal": "Implement core features",
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()

        assert data["name"] == "Sprint 1"
        assert data["number"] == 1
        assert data["goal"] == "Implement core features"
        assert data["status"] == "planned"
        assert data["project_id"] == project_id
        assert "id" in data
        assert "created_at" in data

    async def test_create_sprint_minimal_fields(self, client, user_token, test_project):
        """Test creating sprint with only required fields."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Minimal Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()
        assert data["name"] == "Minimal Sprint"
        assert data["goal"] is None
        assert data["planned_points"] is None

    async def test_create_sprint_invalid_dates(self, client, user_token, test_project):
        """Test that end date before start date is rejected."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date - timedelta(days=1)

        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Invalid Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

    async def test_create_sprint_mismatched_project_id(
        self, client, user_token, test_project
    ):
        """Test that mismatched project IDs are rejected."""
        project_id = test_project["id"]
        fake_project_id = str(uuid.uuid4())
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": fake_project_id,  # Different from URL
                "name": "Mismatched Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

    async def test_create_sprint_unauthorized_project(
        self, client, user_token, superuser_project
    ):
        """Test that users cannot create sprints in others' projects."""
        project_id = superuser_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Unauthorized Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_403_FORBIDDEN

    async def test_create_sprint_nonexistent_project(self, client, user_token):
        """Test creating sprint in nonexistent project."""
        fake_project_id = str(uuid.uuid4())
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        response = await client.post(
            f"/api/v1/projects/{fake_project_id}/sprints",
            json={
                "project_id": fake_project_id,
                "name": "Orphan Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND


@pytest.mark.asyncio
class TestListSprints:
    """Tests for GET /api/v1/projects/{project_id}/sprints endpoint."""

    async def test_list_sprints_empty(self, client, user_token, test_project):
        """Test listing sprints when none exist."""
        project_id = test_project["id"]

        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["data"] == []
        assert data["pagination"]["total"] == 0

    async def test_list_sprints_with_data(self, client, user_token, test_project):
        """Test listing sprints returns created sprints."""
        project_id = test_project["id"]
        start_date = date.today()

        # Create multiple sprints
        for i in range(3):
            end_date = start_date + timedelta(days=14 * (i + 1))
            await client.post(
                f"/api/v1/projects/{project_id}/sprints",
                json={
                    "project_id": project_id,
                    "name": f"Sprint {i + 1}",
                    "number": i + 1,
                    "start_date": start_date.isoformat(),
                    "end_date": end_date.isoformat(),
                },
                headers={"Authorization": f"Bearer {user_token}"},
            )

        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert len(data["data"]) == 3
        assert data["pagination"]["total"] == 3

    async def test_list_sprints_filter_by_status(self, client, user_token, test_project):
        """Test filtering sprints by status."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create a planned sprint
        await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Planned Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Filter by planned status
        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints?status=planned",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert len(data["data"]) >= 1
        assert all(s["status"] == "planned" for s in data["data"])


@pytest.mark.asyncio
class TestGetSprint:
    """Tests for GET /api/v1/projects/{project_id}/sprints/{sprint_id} endpoint."""

    async def test_get_sprint_success(self, client, user_token, test_project):
        """Test getting a sprint by ID."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Get Test Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Get sprint
        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["id"] == sprint_id
        assert data["name"] == "Get Test Sprint"

    async def test_get_sprint_not_found(self, client, user_token, test_project):
        """Test getting a nonexistent sprint."""
        project_id = test_project["id"]
        fake_sprint_id = str(uuid.uuid4())

        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/{fake_sprint_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND


@pytest.mark.asyncio
class TestGetActiveSprint:
    """Tests for GET /api/v1/projects/{project_id}/sprints/active endpoint."""

    async def test_get_active_sprint_none(self, client, user_token, test_project):
        """Test getting active sprint when none exists."""
        project_id = test_project["id"]

        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/active",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json() is None

    async def test_get_active_sprint_exists(self, client, user_token, test_project):
        """Test getting active sprint when one exists."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create and start a sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Active Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Start the sprint
        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Get active sprint
        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/active",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["id"] == sprint_id
        assert data["status"] == "active"


@pytest.mark.asyncio
class TestUpdateSprint:
    """Tests for PATCH /api/v1/projects/{project_id}/sprints/{sprint_id} endpoint."""

    async def test_update_sprint_success(self, client, user_token, test_project):
        """Test updating a sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Original Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Update sprint
        response = await client.patch(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}",
            json={"name": "Updated Sprint", "goal": "New goal"},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["name"] == "Updated Sprint"
        assert data["goal"] == "New goal"

    async def test_update_sprint_not_found(self, client, user_token, test_project):
        """Test updating a nonexistent sprint."""
        project_id = test_project["id"]
        fake_sprint_id = str(uuid.uuid4())

        response = await client.patch(
            f"/api/v1/projects/{project_id}/sprints/{fake_sprint_id}",
            json={"name": "Updated Sprint"},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND


@pytest.mark.asyncio
class TestSprintLifecycle:
    """Tests for sprint lifecycle endpoints (start, complete, cancel)."""

    async def test_start_sprint_success(self, client, user_token, test_project):
        """Test starting a planned sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint to Start",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Start sprint
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json()["status"] == "active"

    async def test_complete_sprint_success(self, client, user_token, test_project):
        """Test completing an active sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create and start sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint to Complete",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Complete sprint
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json()["status"] == "completed"

    async def test_cancel_sprint_success(self, client, user_token, test_project):
        """Test cancelling a planned sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint to Cancel",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Cancel sprint
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/cancel",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json()["status"] == "cancelled"

    async def test_cannot_start_already_active_sprint(
        self, client, user_token, test_project
    ):
        """Test that starting an already active sprint fails."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create and start sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Active Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Try to start again
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

    async def test_cannot_complete_planned_sprint(
        self, client, user_token, test_project
    ):
        """Test that completing a planned sprint fails."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint (planned status)
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Planned Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Try to complete without starting
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY


@pytest.mark.asyncio
class TestDeleteSprint:
    """Tests for DELETE /api/v1/projects/{project_id}/sprints/{sprint_id} endpoint."""

    async def test_delete_planned_sprint_success(self, client, user_token, test_project):
        """Test deleting a planned sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint to Delete",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Delete sprint
        response = await client.delete(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json()["success"] is True

    async def test_delete_cancelled_sprint_success(
        self, client, user_token, test_project
    ):
        """Test deleting a cancelled sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create and cancel sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint to Cancel and Delete",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/cancel",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Delete cancelled sprint
        response = await client.delete(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json()["success"] is True

    async def test_cannot_delete_active_sprint(self, client, user_token, test_project):
        """Test that deleting an active sprint fails."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create and start sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Active Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Try to delete active sprint
        response = await client.delete(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

    async def test_cannot_delete_completed_sprint(
        self, client, user_token, test_project
    ):
        """Test that deleting a completed sprint fails."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create, start, and complete sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Completed Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )
        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Try to delete completed sprint
        response = await client.delete(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY


@pytest.mark.asyncio
class TestSprintVelocity:
    """Tests for GET /api/v1/projects/{project_id}/sprints/velocity endpoint."""

    async def test_get_velocity_empty(self, client, user_token, test_project):
        """Test getting velocity when no completed sprints exist."""
        project_id = test_project["id"]

        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/velocity",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        assert response.json() == []

    async def test_get_velocity_with_completed_sprints(
        self, client, user_token, test_project
    ):
        """Test getting velocity with completed sprints."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create, start, and complete a sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint 1",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
                "planned_points": 20,
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )
        await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/complete",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # Get velocity
        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/velocity",
            headers={"Authorization": f"Bearer {user_token}"},
        )

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


@pytest.mark.asyncio
class TestSprintAuthorization:
    """Tests for sprint authorization."""

    async def test_superuser_can_manage_any_project_sprints(
        self, client, user_token, superuser_token, test_project
    ):
        """Test that superuser can manage sprints in any project."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint as superuser in user's project
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Superuser Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {superuser_token}"},
        )

        assert response.status_code == status.HTTP_201_CREATED

    async def test_user_cannot_access_other_project_sprints(
        self, client, user_token, superuser_project
    ):
        """Test that users cannot access sprints in others' projects."""
        project_id = superuser_project["id"]

        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_403_FORBIDDEN


@pytest.mark.asyncio
class TestSprintIssues:
    """Tests for sprint issue management endpoints."""

    async def test_list_sprint_issues_empty(self, client, user_token, test_project):
        """Test listing issues in a sprint with no issues."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Empty Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # List issues
        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/issues",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["data"] == []
        assert data["pagination"]["total"] == 0

    async def test_list_sprint_issues_with_data(self, client, user_token, test_project):
        """Test listing issues in a sprint with issues."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        create_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Sprint with Issues",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = create_response.json()["id"]

        # Create issue in the sprint
        await client.post(
            f"/api/v1/projects/{project_id}/issues",
            json={
                "project_id": project_id,
                "title": "Sprint Issue 1",
                "sprint_id": sprint_id,
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        await client.post(
            f"/api/v1/projects/{project_id}/issues",
            json={
                "project_id": project_id,
                "title": "Sprint Issue 2",
                "sprint_id": sprint_id,
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )

        # List issues
        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/issues",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert len(data["data"]) == 2
        assert data["pagination"]["total"] == 2

    async def test_list_sprint_issues_not_found(self, client, user_token, test_project):
        """Test listing issues in a nonexistent sprint."""
        project_id = test_project["id"]
        fake_sprint_id = str(uuid.uuid4())

        response = await client.get(
            f"/api/v1/projects/{project_id}/sprints/{fake_sprint_id}/issues",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND

    async def test_add_issue_to_sprint(self, client, user_token, test_project):
        """Test adding an issue to a sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        sprint_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Target Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = sprint_response.json()["id"]

        # Create issue without sprint
        issue_response = await client.post(
            f"/api/v1/projects/{project_id}/issues",
            json={
                "project_id": project_id,
                "title": "Issue to Add",
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        issue_id = issue_response.json()["id"]

        # Add issue to sprint
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/issues",
            params={"issue_id": issue_id},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_200_OK

    async def test_add_nonexistent_issue_to_sprint(
        self, client, user_token, test_project
    ):
        """Test adding a nonexistent issue to a sprint."""
        project_id = test_project["id"]
        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint
        sprint_response = await client.post(
            f"/api/v1/projects/{project_id}/sprints",
            json={
                "project_id": project_id,
                "name": "Target Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = sprint_response.json()["id"]
        fake_issue_id = str(uuid.uuid4())

        # Try to add nonexistent issue
        response = await client.post(
            f"/api/v1/projects/{project_id}/sprints/{sprint_id}/issues",
            params={"issue_id": fake_issue_id},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND


@pytest.mark.asyncio
class TestSprintCrossProjectValidation:
    """Tests for cross-project validation (IDOR prevention)."""

    async def test_sprint_not_in_project(self, client, user_token):
        """Test accessing sprint that exists but not in the specified project."""
        # Create two projects
        project1 = await client.post(
            "/api/v1/projects",
            json={"name": "Project 1 Sprint", "slug": "project-1-sprint-idor"},
            headers={"Authorization": f"Bearer {user_token}"},
        )
        project2 = await client.post(
            "/api/v1/projects",
            json={"name": "Project 2 Sprint", "slug": "project-2-sprint-idor"},
            headers={"Authorization": f"Bearer {user_token}"},
        )
        project1_id = project1.json()["id"]
        project2_id = project2.json()["id"]

        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint in project1
        sprint_response = await client.post(
            f"/api/v1/projects/{project1_id}/sprints",
            json={
                "project_id": project1_id,
                "name": "Project 1 Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = sprint_response.json()["id"]

        # Try to access sprint via project2 (IDOR attempt)
        response = await client.get(
            f"/api/v1/projects/{project2_id}/sprints/{sprint_id}",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND

    async def test_update_sprint_wrong_project(self, client, user_token):
        """Test updating sprint through wrong project."""
        # Create two projects
        project1 = await client.post(
            "/api/v1/projects",
            json={"name": "Project A Sprint", "slug": "project-a-sprint-idor"},
            headers={"Authorization": f"Bearer {user_token}"},
        )
        project2 = await client.post(
            "/api/v1/projects",
            json={"name": "Project B Sprint", "slug": "project-b-sprint-idor"},
            headers={"Authorization": f"Bearer {user_token}"},
        )
        project1_id = project1.json()["id"]
        project2_id = project2.json()["id"]

        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint in project1
        sprint_response = await client.post(
            f"/api/v1/projects/{project1_id}/sprints",
            json={
                "project_id": project1_id,
                "name": "Project A Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = sprint_response.json()["id"]

        # Try to update sprint via project2 (IDOR attempt)
        response = await client.patch(
            f"/api/v1/projects/{project2_id}/sprints/{sprint_id}",
            json={"name": "Hacked Name"},
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND

    async def test_start_sprint_wrong_project(self, client, user_token):
        """Test starting sprint through wrong project."""
        # Create two projects
        project1 = await client.post(
            "/api/v1/projects",
            json={"name": "Project X Sprint", "slug": "project-x-sprint-idor"},
            headers={"Authorization": f"Bearer {user_token}"},
        )
        project2 = await client.post(
            "/api/v1/projects",
            json={"name": "Project Y Sprint", "slug": "project-y-sprint-idor"},
            headers={"Authorization": f"Bearer {user_token}"},
        )
        project1_id = project1.json()["id"]
        project2_id = project2.json()["id"]

        start_date = date.today()
        end_date = start_date + timedelta(days=14)

        # Create sprint in project1
        sprint_response = await client.post(
            f"/api/v1/projects/{project1_id}/sprints",
            json={
                "project_id": project1_id,
                "name": "Project X Sprint",
                "number": 1,
                "start_date": start_date.isoformat(),
                "end_date": end_date.isoformat(),
            },
            headers={"Authorization": f"Bearer {user_token}"},
        )
        sprint_id = sprint_response.json()["id"]

        # Try to start sprint via project2 (IDOR attempt)
        response = await client.post(
            f"/api/v1/projects/{project2_id}/sprints/{sprint_id}/start",
            headers={"Authorization": f"Bearer {user_token}"},
        )

        assert response.status_code == status.HTTP_404_NOT_FOUND
