"""
Tests for EduSearch Seeds API.

Tests cover:
- CRUD operations for seeds
- Category management
- Bulk import functionality
- Statistics endpoint
- Error handling
"""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from fastapi import HTTPException
from fastapi.testclient import TestClient
import uuid
from datetime import datetime


class AsyncContextManagerMock:
    """Helper class to create proper async context managers for testing."""

    def __init__(self, return_value):
        self.return_value = return_value

    async def __aenter__(self):
        return self.return_value

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        return None


def create_mock_pool_and_conn():
    """Helper to create properly configured mock pool and connection for async tests."""
    mock_pool = MagicMock()
    mock_conn = AsyncMock()

    # Configure pool.acquire() to return an async context manager
    mock_pool.acquire.return_value = AsyncContextManagerMock(mock_conn)

    return mock_pool, mock_conn


def mock_db_pool_patch(mock_pool):
    """Create a patch for get_db_pool that returns mock_pool."""
    async def _mock_get_pool():
        return mock_pool
    return patch("llm_gateway.routes.edu_search_seeds.get_db_pool", new=_mock_get_pool)


class TestSeedModels:
    """Test Pydantic models for seeds."""

    def test_seed_base_valid(self):
        """Test SeedBase with valid data."""
        from llm_gateway.routes.edu_search_seeds import SeedBase

        seed = SeedBase(
            url="https://www.kmk.org",
            name="KMK",
            description="Test description",
            trust_boost=0.95,
            enabled=True
        )
        assert seed.url == "https://www.kmk.org"
        assert seed.name == "KMK"
        assert seed.trust_boost == 0.95

    def test_seed_base_defaults(self):
        """Test SeedBase default values."""
        from llm_gateway.routes.edu_search_seeds import SeedBase

        seed = SeedBase(
            url="https://test.de",
            name="Test"
        )
        assert seed.description is None
        assert seed.trust_boost == 0.5
        assert seed.enabled is True
        assert seed.source_type == "GOV"
        assert seed.scope == "FEDERAL"

    def test_seed_create_model(self):
        """Test SeedCreate model."""
        from llm_gateway.routes.edu_search_seeds import SeedCreate

        seed = SeedCreate(
            url="https://www.test.de",
            name="Test Seed",
            category_name="federal"
        )
        assert seed.url == "https://www.test.de"
        assert seed.category_name == "federal"

    def test_seed_update_model(self):
        """Test SeedUpdate with partial data."""
        from llm_gateway.routes.edu_search_seeds import SeedUpdate

        seed = SeedUpdate(enabled=False)
        assert seed.enabled is False
        assert seed.name is None
        assert seed.url is None


class TestCategoryResponse:
    """Test category response models."""

    def test_category_response(self):
        """Test CategoryResponse model."""
        from llm_gateway.routes.edu_search_seeds import CategoryResponse

        cat = CategoryResponse(
            id="550e8400-e29b-41d4-a716-446655440000",
            name="federal",
            display_name="Bundesebene",
            description="KMK, BMBF",
            icon="icon",
            sort_order=0,
            is_active=True
        )
        assert cat.name == "federal"
        assert cat.display_name == "Bundesebene"


class TestDatabaseConnection:
    """Test database connection handling."""

    @pytest.mark.asyncio
    async def test_get_db_pool_creates_pool(self):
        """Test that get_db_pool creates a connection pool."""
        from llm_gateway.routes.edu_search_seeds import get_db_pool
        import llm_gateway.routes.edu_search_seeds as module

        mock_pool = MagicMock()

        # Reset global pool
        module._pool = None

        with patch("llm_gateway.routes.edu_search_seeds.asyncpg.create_pool",
                   new=AsyncMock(return_value=mock_pool)) as mock_create:
            with patch.dict("os.environ", {"DATABASE_URL": "postgresql://test:test@localhost/test"}):
                pool = await get_db_pool()
                mock_create.assert_called_once()
                assert pool == mock_pool

        # Cleanup
        module._pool = None

    @pytest.mark.asyncio
    async def test_get_db_pool_reuses_existing(self):
        """Test that get_db_pool reuses existing pool."""
        from llm_gateway.routes.edu_search_seeds import get_db_pool

        import llm_gateway.routes.edu_search_seeds as module
        mock_pool = MagicMock()
        module._pool = mock_pool

        pool = await get_db_pool()
        assert pool == mock_pool

        # Cleanup
        module._pool = None


class TestListCategories:
    """Test list_categories endpoint."""

    @pytest.mark.asyncio
    async def test_list_categories_success(self):
        """Test successful category listing."""
        from llm_gateway.routes.edu_search_seeds import list_categories

        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.fetch.return_value = [
            {
                "id": uuid.uuid4(),
                "name": "federal",
                "display_name": "Bundesebene",
                "description": "Test",
                "icon": "icon",
                "sort_order": 0,
                "is_active": True,
                "created_at": "2024-01-01T00:00:00Z"
            }
        ]

        with mock_db_pool_patch(mock_pool):
            result = await list_categories()

        assert len(result) == 1
        assert result[0].name == "federal"

    @pytest.mark.asyncio
    async def test_list_categories_empty(self):
        """Test category listing with no categories."""
        from llm_gateway.routes.edu_search_seeds import list_categories

        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.fetch.return_value = []

        with mock_db_pool_patch(mock_pool):
            result = await list_categories()

        assert result == []


class TestGetSeed:
    """Test get_seed endpoint."""

    @pytest.mark.asyncio
    async def test_get_seed_found(self):
        """Test getting existing seed."""
        from llm_gateway.routes.edu_search_seeds import get_seed

        seed_id = str(uuid.uuid4())
        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.fetchrow.return_value = {
            "id": seed_id,
            "url": "https://test.de",
            "name": "Test",
            "description": None,
            "category": "federal",
            "category_display_name": "Bundesebene",
            "source_type": "GOV",
            "scope": "FEDERAL",
            "state": None,
            "trust_boost": 0.5,
            "enabled": True,
            "crawl_depth": 2,
            "crawl_frequency": "weekly",
            "last_crawled_at": None,
            "last_crawl_status": None,
            "last_crawl_docs": 0,
            "total_documents": 0,
            "created_at": datetime.now(),
            "updated_at": datetime.now()
        }

        with mock_db_pool_patch(mock_pool):
            result = await get_seed(seed_id)

        assert result.url == "https://test.de"
        assert result.category == "federal"

    @pytest.mark.asyncio
    async def test_get_seed_not_found(self):
        """Test getting non-existing seed returns 404."""
        from llm_gateway.routes.edu_search_seeds import get_seed

        seed_id = str(uuid.uuid4())
        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.fetchrow.return_value = None

        with mock_db_pool_patch(mock_pool):
            with pytest.raises(HTTPException) as exc_info:
                await get_seed(seed_id)

        assert exc_info.value.status_code == 404


class TestCreateSeed:
    """Test create_seed endpoint."""

    @pytest.mark.asyncio
    async def test_create_seed_success(self):
        """Test successful seed creation."""
        from llm_gateway.routes.edu_search_seeds import create_seed, SeedCreate

        new_seed = SeedCreate(
            url="https://new-seed.de",
            name="New Seed",
            description="Test seed",
            trust_boost=0.8
        )

        mock_pool, mock_conn = create_mock_pool_and_conn()
        new_id = uuid.uuid4()
        now = datetime.now()
        # Mock for fetchval (category lookup - returns None since no category)
        mock_conn.fetchval.return_value = None
        # Mock for fetchrow (insert returning)
        mock_conn.fetchrow.return_value = {
            "id": new_id,
            "created_at": now,
            "updated_at": now
        }

        with mock_db_pool_patch(mock_pool):
            result = await create_seed(new_seed)

        assert result.id == str(new_id)
        assert result.url == "https://new-seed.de"

    @pytest.mark.asyncio
    async def test_create_seed_duplicate_url(self):
        """Test creating seed with duplicate URL fails with 409."""
        from llm_gateway.routes.edu_search_seeds import create_seed, SeedCreate
        import asyncpg

        new_seed = SeedCreate(
            url="https://duplicate.de",
            name="Duplicate"
        )

        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.fetchval.return_value = None  # No category
        mock_conn.fetchrow.side_effect = asyncpg.UniqueViolationError("duplicate key")

        with mock_db_pool_patch(mock_pool):
            with pytest.raises(HTTPException) as exc_info:
                await create_seed(new_seed)

        assert exc_info.value.status_code == 409
        assert "existiert bereits" in exc_info.value.detail


class TestUpdateSeed:
    """Test update_seed endpoint."""

    @pytest.mark.asyncio
    async def test_update_seed_success(self):
        """Test successful seed update."""
        from llm_gateway.routes.edu_search_seeds import update_seed, SeedUpdate

        seed_id = str(uuid.uuid4())
        update_data = SeedUpdate(name="Updated Name")

        mock_pool, mock_conn = create_mock_pool_and_conn()
        now = datetime.now()
        # Mock for execute (update) - returns non-zero
        mock_conn.execute.return_value = "UPDATE 1"
        # Mock for fetchval (check if update succeeded)
        mock_conn.fetchval.return_value = seed_id
        # Mock for fetchrow (get_seed after update)
        mock_conn.fetchrow.return_value = {
            "id": seed_id,
            "url": "https://test.de",
            "name": "Updated Name",
            "description": None,
            "category": "federal",
            "category_display_name": "Bundesebene",
            "source_type": "GOV",
            "scope": "FEDERAL",
            "state": None,
            "trust_boost": 0.5,
            "enabled": True,
            "crawl_depth": 2,
            "crawl_frequency": "weekly",
            "last_crawled_at": None,
            "last_crawl_status": None,
            "last_crawl_docs": 0,
            "total_documents": 0,
            "created_at": now,
            "updated_at": now
        }

        with mock_db_pool_patch(mock_pool):
            result = await update_seed(seed_id, update_data)

        assert result.name == "Updated Name"

    @pytest.mark.asyncio
    async def test_update_seed_not_found(self):
        """Test updating non-existing seed returns 404."""
        from llm_gateway.routes.edu_search_seeds import update_seed, SeedUpdate

        seed_id = str(uuid.uuid4())
        update_data = SeedUpdate(name="Updated")

        mock_pool, mock_conn = create_mock_pool_and_conn()
        # UPDATE uses fetchrow with RETURNING id - returns None for not found
        mock_conn.fetchrow.return_value = None

        with mock_db_pool_patch(mock_pool):
            with pytest.raises(HTTPException) as exc_info:
                await update_seed(seed_id, update_data)

        assert exc_info.value.status_code == 404

    @pytest.mark.asyncio
    async def test_update_seed_empty_update(self):
        """Test update with no fields returns 400."""
        from llm_gateway.routes.edu_search_seeds import update_seed, SeedUpdate

        seed_id = str(uuid.uuid4())
        update_data = SeedUpdate()

        mock_pool, mock_conn = create_mock_pool_and_conn()

        with mock_db_pool_patch(mock_pool):
            with pytest.raises(HTTPException) as exc_info:
                await update_seed(seed_id, update_data)

        assert exc_info.value.status_code == 400


class TestDeleteSeed:
    """Test delete_seed endpoint."""

    @pytest.mark.asyncio
    async def test_delete_seed_success(self):
        """Test successful seed deletion."""
        from llm_gateway.routes.edu_search_seeds import delete_seed

        seed_id = str(uuid.uuid4())
        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.execute.return_value = "DELETE 1"

        with mock_db_pool_patch(mock_pool):
            result = await delete_seed(seed_id)

        assert result["status"] == "deleted"
        assert result["id"] == seed_id

    @pytest.mark.asyncio
    async def test_delete_seed_not_found(self):
        """Test deleting non-existing seed returns 404."""
        from llm_gateway.routes.edu_search_seeds import delete_seed

        seed_id = str(uuid.uuid4())
        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.execute.return_value = "DELETE 0"

        with mock_db_pool_patch(mock_pool):
            with pytest.raises(HTTPException) as exc_info:
                await delete_seed(seed_id)

        assert exc_info.value.status_code == 404


class TestBulkImport:
    """Test bulk_import_seeds endpoint."""

    @pytest.mark.asyncio
    async def test_bulk_import_success(self):
        """Test successful bulk import."""
        from llm_gateway.routes.edu_search_seeds import bulk_import_seeds, BulkImportRequest, SeedCreate

        seeds = [
            SeedCreate(url="https://test1.de", name="Test 1", category_name="federal"),
            SeedCreate(url="https://test2.de", name="Test 2", category_name="states")
        ]
        request = BulkImportRequest(seeds=seeds)

        mock_pool, mock_conn = create_mock_pool_and_conn()

        # Mock category pre-fetch
        mock_conn.fetch.return_value = [
            {"id": uuid.uuid4(), "name": "federal"},
            {"id": uuid.uuid4(), "name": "states"}
        ]
        # Mock inserts - ON CONFLICT DO NOTHING, no exception
        mock_conn.execute.return_value = "INSERT 0 1"

        with mock_db_pool_patch(mock_pool):
            result = await bulk_import_seeds(request)

        assert result.imported == 2
        assert result.skipped == 0

    @pytest.mark.asyncio
    async def test_bulk_import_with_errors(self):
        """Test bulk import handles errors gracefully."""
        from llm_gateway.routes.edu_search_seeds import bulk_import_seeds, BulkImportRequest, SeedCreate

        seeds = [SeedCreate(url="https://error.de", name="Error Seed")]
        request = BulkImportRequest(seeds=seeds)

        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.fetch.return_value = []  # No categories
        mock_conn.execute.side_effect = Exception("Database error")

        with mock_db_pool_patch(mock_pool):
            result = await bulk_import_seeds(request)

        assert result.imported == 0
        assert len(result.errors) == 1


class TestGetStats:
    """Test get_stats endpoint."""

    @pytest.mark.asyncio
    async def test_get_stats_success(self):
        """Test successful stats retrieval."""
        from llm_gateway.routes.edu_search_seeds import get_stats

        mock_pool, mock_conn = create_mock_pool_and_conn()
        # Mock for multiple fetchval calls
        mock_conn.fetchval.side_effect = [56, 52, 1000, None]  # total, enabled, total_docs, last_crawl
        # Mock for fetch calls (by_category, by_state)
        mock_conn.fetch.side_effect = [
            [{"name": "federal", "count": 5}],  # per category
            [{"state": "BY", "count": 5}]  # per state
        ]

        with mock_db_pool_patch(mock_pool):
            result = await get_stats()

        assert result.total_seeds == 56
        assert result.enabled_seeds == 52
        assert result.total_documents == 1000


class TestExportForCrawler:
    """Test export_seeds_for_crawler endpoint."""

    @pytest.mark.asyncio
    async def test_export_for_crawler_success(self):
        """Test successful crawler export."""
        from llm_gateway.routes.edu_search_seeds import export_seeds_for_crawler

        mock_pool, mock_conn = create_mock_pool_and_conn()
        mock_conn.fetch.return_value = [
            {
                "url": "https://test.de",
                "trust_boost": 0.9,
                "source_type": "GOV",
                "scope": "FEDERAL",
                "state": None,
                "crawl_depth": 2,
                "category": "federal"
            }
        ]

        with mock_db_pool_patch(mock_pool):
            result = await export_seeds_for_crawler()

        assert "seeds" in result
        assert len(result["seeds"]) == 1
        assert "exported_at" in result
        assert result["total"] == 1
        assert result["seeds"][0]["trust"] == 0.9


class TestEdgeCases:
    """Test edge cases and error handling."""

    @pytest.mark.asyncio
    async def test_invalid_uuid_format(self):
        """Test handling of invalid UUID format."""
        from llm_gateway.routes.edu_search_seeds import get_seed
        import asyncpg

        mock_pool, mock_conn = create_mock_pool_and_conn()

        # asyncpg raises DataError for invalid UUID
        mock_conn.fetchrow.side_effect = asyncpg.DataError("invalid UUID")

        with mock_db_pool_patch(mock_pool):
            with pytest.raises(asyncpg.DataError):
                await get_seed("not-a-uuid")

    @pytest.mark.asyncio
    async def test_database_connection_error(self):
        """Test handling of database connection errors."""
        from llm_gateway.routes.edu_search_seeds import list_categories

        async def _failing_get_pool():
            raise Exception("Connection failed")

        with patch("llm_gateway.routes.edu_search_seeds.get_db_pool", new=_failing_get_pool):
            with pytest.raises(Exception) as exc_info:
                await list_categories()

        assert "Connection failed" in str(exc_info.value)

    def test_trust_boost_validation(self):
        """Test trust_boost must be between 0 and 1."""
        from llm_gateway.routes.edu_search_seeds import SeedBase
        from pydantic import ValidationError

        # Valid values
        seed = SeedBase(url="https://test.de", name="Test", trust_boost=0.5)
        assert seed.trust_boost == 0.5

        # Edge values
        seed_min = SeedBase(url="https://test.de", name="Test", trust_boost=0.0)
        assert seed_min.trust_boost == 0.0

        seed_max = SeedBase(url="https://test.de", name="Test", trust_boost=1.0)
        assert seed_max.trust_boost == 1.0

        # Invalid values
        with pytest.raises(ValidationError):
            SeedBase(url="https://test.de", name="Test", trust_boost=1.5)

        with pytest.raises(ValidationError):
            SeedBase(url="https://test.de", name="Test", trust_boost=-0.1)
