"""Tests for project CRUD API routes."""

import uuid
from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock

import pytest
from httpx import AsyncClient


class TestProjects:
    async def test_create_project_success(
        self, client: AsyncClient, mock_session: AsyncMock
    ):
        mock_result = MagicMock()
        mock_result.scalar.return_value = 0
        mock_session.execute.return_value = mock_result

        async def fake_flush(*args, **kwargs):
            proj = mock_session.add.call_args[0][0]
            proj.id = uuid.uuid4()
            proj.created_at = datetime.now(UTC)

        mock_session.flush.side_effect = fake_flush

        response = await client.post(
            "/projects/", json={"name": "New Project", "target_metric": "revenue"}
        )

        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "New Project"
        assert "id" in data
        assert "created_at" in data
        mock_session.add.assert_called_once()
        mock_session.flush.assert_awaited_once()

    async def test_create_project_free_tier_limit(
        self, client: AsyncClient, mock_session: AsyncMock
    ):
        mock_result = MagicMock()
        mock_result.scalar.return_value = 3
        mock_session.execute.return_value = mock_result

        response = await client.post("/projects/", json={"name": "Fail Project"})

        assert response.status_code == 403
        assert "limit" in response.json()["detail"].lower()
        mock_session.add.assert_not_called()

    async def test_list_projects(
        self, client: AsyncClient, mock_session: AsyncMock
    ):
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute.return_value = mock_result

        response = await client.get("/projects/")

        assert response.status_code == 200
        assert response.json() == []

    async def test_get_project_found(
        self, client: AsyncClient, mock_session: AsyncMock
    ):
        proj_id = uuid.uuid4()
        mock_project = MagicMock()
        mock_project.id = proj_id
        mock_project.name = "Test Project"
        mock_project.target_metric = "revenue"
        mock_project.currency = "USD"
        mock_project.created_at = datetime.now(UTC)

        proj_result = MagicMock()
        proj_result.scalar_one_or_none.return_value = mock_project

        count_result = MagicMock()
        count_result.scalar.return_value = 2

        status_result = MagicMock()
        status_result.scalar_one_or_none.return_value = "completed"

        mock_session.execute.side_effect = [proj_result, count_result, status_result]

        response = await client.get(f"/projects/{proj_id}")

        assert response.status_code == 200
        data = response.json()
        assert data["id"] == str(proj_id)
        assert data["name"] == "Test Project"
        assert data["dataset_count"] == 2
        assert data["latest_run_status"] == "completed"

    async def test_get_project_not_found(
        self, client: AsyncClient, mock_session: AsyncMock
    ):
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result

        response = await client.get(f"/projects/{uuid.uuid4()}")

        assert response.status_code == 404

    async def test_delete_project_success(
        self, client: AsyncClient, mock_session: AsyncMock
    ):
        proj_id = uuid.uuid4()
        mock_project = MagicMock()
        mock_project.id = proj_id

        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_project
        mock_session.execute.return_value = mock_result

        response = await client.delete(f"/projects/{proj_id}")

        assert response.status_code == 204
        mock_session.delete.assert_awaited_once_with(mock_project)
