"""Tests for GeminiDistiller — Gemini-based knowledge distillation."""

import json
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from neo_cortex.distiller import GeminiDistiller, KnowledgeEntry


def _mock_gemini_response(entries: list[dict]) -> MagicMock:
    """Create a mock httpx response with Gemini format."""
    resp = MagicMock()
    resp.status_code = 200
    resp.raise_for_status = MagicMock()
    resp.json.return_value = {
        "candidates": [{
            "content": {
                "parts": [{"text": json.dumps(entries)}]
            }
        }]
    }
    return resp


class TestDistillParsing:
    @pytest.mark.asyncio
    async def test_distill_parses_valid_json(self):
        """Valid Gemini response → list of KnowledgeEntry."""
        entries = [
            {"topic": "CORS fix", "content": "Added UseCors() middleware.", "type": "bugfix", "project": "neo-reloaded"},
            {"topic": "Version bump", "content": "Bumped to 5.2.0.", "type": "config", "project": "neo-cortex"},
        ]
        d = GeminiDistiller("fake-key")
        d._client = AsyncMock()
        d._client.post = AsyncMock(return_value=_mock_gemini_response(entries))

        result = await d.distill(["User: fix CORS\nAssistant: Done"])

        assert len(result) == 2
        assert isinstance(result[0], KnowledgeEntry)
        assert result[0].topic == "CORS fix"
        assert result[0].project == "neo-reloaded"
        assert result[1].type == "config"

    @pytest.mark.asyncio
    async def test_distill_handles_api_error(self):
        """Gemini API error → empty list, no crash."""
        d = GeminiDistiller("fake-key")
        d._client = AsyncMock()
        d._client.post = AsyncMock(side_effect=Exception("API down"))

        result = await d.distill(["User: hello\nAssistant: hi"])
        assert result == []

    @pytest.mark.asyncio
    async def test_distill_skips_invalid_entries(self):
        """Entries without topic/content are skipped."""
        entries = [
            {"topic": "Good entry", "content": "This is valid.", "type": "feature", "project": "general"},
            {"topic": "", "content": "No topic"},
            {"content": "No topic field at all"},
            {"topic": "No content"},
            {"topic": "Also good", "content": "Second valid.", "type": "bugfix", "project": "neo-cortex"},
        ]
        d = GeminiDistiller("fake-key")
        d._client = AsyncMock()
        d._client.post = AsyncMock(return_value=_mock_gemini_response(entries))

        result = await d.distill(["User: test"])
        assert len(result) == 2
        assert result[0].topic == "Good entry"
        assert result[1].topic == "Also good"

    @pytest.mark.asyncio
    async def test_distill_empty_input(self):
        """Empty turns list → empty result, no API call."""
        d = GeminiDistiller("fake-key")
        d._client = AsyncMock()
        d._client.post = AsyncMock()

        result = await d.distill([])
        assert result == []
        d._client.post.assert_not_called()

    @pytest.mark.asyncio
    async def test_distill_handles_non_json_response(self):
        """Gemini returns text instead of JSON → empty list."""
        resp = MagicMock()
        resp.status_code = 200
        resp.raise_for_status = MagicMock()
        resp.json.return_value = {
            "candidates": [{
                "content": {
                    "parts": [{"text": "I cannot process this conversation."}]
                }
            }]
        }
        d = GeminiDistiller("fake-key")
        d._client = AsyncMock()
        d._client.post = AsyncMock(return_value=resp)

        result = await d.distill(["User: test"])
        assert result == []

    @pytest.mark.asyncio
    async def test_distill_defaults_type_and_project(self):
        """Missing type/project fields get defaults."""
        entries = [
            {"topic": "Some topic", "content": "Some content."},
        ]
        d = GeminiDistiller("fake-key")
        d._client = AsyncMock()
        d._client.post = AsyncMock(return_value=_mock_gemini_response(entries))

        result = await d.distill(["User: test"])
        assert len(result) == 1
        assert result[0].type == "feature"
        assert result[0].project == "general"


class TestKnowledgeEntry:
    def test_model_fields(self):
        e = KnowledgeEntry(topic="Test", content="Content", type="bugfix", project="neo-cortex")
        assert e.topic == "Test"
        assert e.content == "Content"
        assert e.type == "bugfix"
        assert e.project == "neo-cortex"

    def test_defaults(self):
        e = KnowledgeEntry(topic="Test", content="Content")
        assert e.type == "feature"
        assert e.project == "general"
