"""Tests for Distiller — provider-agnostic knowledge distillation."""
import json
from unittest.mock import AsyncMock

import pytest

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


class MockDistillerLLM(LLMClient):
    """Mock LLM that returns pre-configured text."""

    def __init__(self, response: str = "[]"):
        super().__init__()
        self._response = response
        self.call_count = 0
        self.last_prompt = None
        self.last_text = None
        self.last_max_tokens = None

    @property
    def name(self) -> str:
        return "mock/distiller"

    async def call(self, prompt: str, text: str, max_tokens: int = 250, temperature: float = 0.1) -> str:
        self.call_count += 1
        self.last_prompt = prompt
        self.last_text = text
        self.last_max_tokens = max_tokens
        return self._response

    async def close(self) -> None:
        pass


class TestDistillerGeneric:
    def test_accepts_any_llm_client(self):
        llm = MockDistillerLLM()
        d = Distiller(llm)
        assert d._llm is llm

    @pytest.mark.asyncio
    async def test_distill_calls_llm_with_prompt_and_text(self):
        entries = [{"topic": "test", "content": "test content", "type": "feature",
                    "project": "general", "concepts": ["foo"]}]
        llm = MockDistillerLLM(json.dumps(entries))
        d = Distiller(llm)

        result = await d.distill(["turn 1", "turn 2"])

        assert len(result) == 1
        assert result[0].topic == "test"
        assert llm.call_count == 1
        assert "KNOWLEDGE DISTILLER" in llm.last_prompt
        assert "turn 1" in llm.last_text
        assert "turn 2" in llm.last_text

    @pytest.mark.asyncio
    async def test_distill_sends_prompt_as_prompt_text_as_text(self):
        """Prompt and text must be separated — prompt=DISTILL_PROMPT, text=turns."""
        llm = MockDistillerLLM("[]")
        d = Distiller(llm)
        await d.distill(["some conversation turn"])
        assert "KNOWLEDGE DISTILLER" in llm.last_prompt
        assert "some conversation turn" in llm.last_text
        assert "some conversation turn" not in llm.last_prompt

    @pytest.mark.asyncio
    async def test_distill_uses_high_max_tokens(self):
        llm = MockDistillerLLM("[]")
        d = Distiller(llm)
        await d.distill(["turn 1"])
        assert llm.last_max_tokens >= 8000

    @pytest.mark.asyncio
    async def test_distill_empty_turns(self):
        llm = MockDistillerLLM()
        d = Distiller(llm)
        assert await d.distill([]) == []
        assert llm.call_count == 0

    @pytest.mark.asyncio
    async def test_distill_handles_llm_error(self):
        llm = MockDistillerLLM()
        llm.call = AsyncMock(side_effect=Exception("API down"))
        d = Distiller(llm)
        result = await d.distill(["turn 1"])
        assert result == []

    @pytest.mark.asyncio
    async def test_distill_handles_bad_json(self):
        llm = MockDistillerLLM("not json at all")
        d = Distiller(llm)
        result = await d.distill(["turn 1"])
        assert result == []

    @pytest.mark.asyncio
    async def test_distill_skips_invalid_entries(self):
        entries = [
            {"topic": "Good", "content": "Valid.", "type": "feature"},
            {"topic": "", "content": "No topic"},
            {"topic": "Also good", "content": "Second valid."},
        ]
        llm = MockDistillerLLM(json.dumps(entries))
        d = Distiller(llm)
        result = await d.distill(["turn"])
        assert len(result) == 2
        assert result[0].topic == "Good"
        assert result[1].topic == "Also good"

    @pytest.mark.asyncio
    async def test_distill_handles_non_json_text_response(self):
        llm = MockDistillerLLM("I cannot process this conversation.")
        d = Distiller(llm)
        result = await d.distill(["turn"])
        assert result == []

    @pytest.mark.asyncio
    async def test_distill_defaults_type_and_project(self):
        entries = [{"topic": "Test", "content": "Content."}]
        llm = MockDistillerLLM(json.dumps(entries))
        d = Distiller(llm)
        result = await d.distill(["turn"])
        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"
