"""Tests for Distiller — LLMClient-based knowledge distillation."""

import json
from unittest.mock import AsyncMock, MagicMock

import pytest

from neo_cortex.distiller import Distiller, GeminiDistiller, KnowledgeEntry
from neo_cortex.llm import LLMClient


def _make_llm(response: str) -> AsyncMock:
    """Create a mock LLMClient that returns the given string."""
    mock = AsyncMock(spec=LLMClient)
    mock.name = "mock/test"
    mock.call = AsyncMock(return_value=response)
    return mock


class TestDistillParsing:
    @pytest.mark.asyncio
    async def test_distill_parses_valid_json(self):
        """Valid LLM response → list of KnowledgeEntry."""
        entries = [
            {"topic": "CORS fix", "content": "Added UseCors() middleware.", "type": "bugfix", "project": "neo-reloaded", "concepts": ["CORS"]},
            {"topic": "Version bump", "content": "Bumped to 5.2.0.", "type": "config", "project": "neo-cortex", "concepts": ["versioning"]},
        ]
        d = Distiller(_make_llm(json.dumps(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):
        """LLM error → empty list, no crash."""
        mock_llm = AsyncMock(spec=LLMClient)
        mock_llm.name = "mock/test"
        mock_llm.call = AsyncMock(side_effect=Exception("API down"))
        d = Distiller(mock_llm)

        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", "concepts": ["x"]},
            {"topic": "", "content": "No topic"},
            {"content": "No topic field at all"},
            {"topic": "No content"},
            {"topic": "Also good", "content": "Second valid.", "type": "bugfix", "project": "neo-cortex", "concepts": ["y"]},
        ]
        d = Distiller(_make_llm(json.dumps(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 LLM call."""
        mock_llm = _make_llm("[]")
        d = Distiller(mock_llm)

        result = await d.distill([])
        assert result == []
        mock_llm.call.assert_not_called()

    @pytest.mark.asyncio
    async def test_distill_handles_non_json_response(self):
        """LLM returns text instead of JSON → empty list."""
        d = Distiller(_make_llm("I cannot process this conversation."))

        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 = Distiller(_make_llm(json.dumps(entries)))

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

    def test_gemini_distiller_is_alias(self):
        """GeminiDistiller is still importable and is the same class."""
        assert GeminiDistiller is Distiller


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"
