import os

import pytest

from pydstl import Evidence


class TestAddEvidence:
    def test_returns_id(self, dstl_instance):
        eid = dstl_instance.add_evidence("Python uses indentation for blocks")
        assert isinstance(eid, int)
        assert eid >= 1

    def test_increments_id(self, dstl_instance):
        e1 = dstl_instance.add_evidence("fact one")
        e2 = dstl_instance.add_evidence("fact two")
        assert e2 > e1

    def test_with_source_metadata(self, dstl_instance):
        eid = dstl_instance.add_evidence(
            "Use pytest for testing",
            source={"author": "Alice", "url": "https://example.com"},
        )
        assert eid >= 1


class TestRetrieve:
    def test_returns_evidence_list(self, dstl_instance):
        dstl_instance.add_evidence("Python error handling with try/except")
        dstl_instance.add_evidence("JavaScript uses try/catch for errors")
        dstl_instance.add_evidence("Cooking pasta requires boiling water")

        results = dstl_instance.retrieve("error handling", top_k=2)
        assert len(results) <= 2
        assert all(isinstance(e, Evidence) for e in results)

    def test_returns_scores(self, dstl_instance):
        dstl_instance.add_evidence("test content")
        results = dstl_instance.retrieve("test")
        assert len(results) >= 1
        assert results[0].score is not None

    def test_empty_store(self, dstl_instance):
        results = dstl_instance.retrieve("anything")
        assert results == []


class TestListEvidence:
    def test_list_all(self, dstl_instance):
        dstl_instance.add_evidence("fact one", source={"author": "Alice"})
        dstl_instance.add_evidence("fact two", source={"author": "Bob"})

        results = dstl_instance.list_evidence()
        assert len(results) == 2

    def test_filter_by_source(self, dstl_instance):
        dstl_instance.add_evidence("fact one", source={"author": "Alice", "type": "blog"})
        dstl_instance.add_evidence("fact two", source={"author": "Bob", "type": "paper"})
        dstl_instance.add_evidence("fact three", source={"author": "Alice", "type": "paper"})

        results = dstl_instance.list_evidence(source_filter={"author": "Alice"})
        assert len(results) == 2
        assert all(e.source["author"] == "Alice" for e in results)

    def test_filter_multiple_keys(self, dstl_instance):
        dstl_instance.add_evidence("fact one", source={"author": "Alice", "type": "blog"})
        dstl_instance.add_evidence("fact two", source={"author": "Alice", "type": "paper"})

        results = dstl_instance.list_evidence(source_filter={"author": "Alice", "type": "paper"})
        assert len(results) == 1
        assert results[0].content == "fact two"

    def test_filter_no_match(self, dstl_instance):
        dstl_instance.add_evidence("fact one", source={"author": "Alice"})
        results = dstl_instance.list_evidence(source_filter={"author": "Charlie"})
        assert results == []

    def test_filter_numeric_value(self, dstl_instance):
        dstl_instance.add_evidence("fact one", source={"pr": 42, "author": "Alice"})
        dstl_instance.add_evidence("fact two", source={"pr": 99, "author": "Bob"})

        results = dstl_instance.list_evidence(source_filter={"pr": 42})
        assert len(results) == 1
        assert results[0].content == "fact one"

    def test_filter_boolean_value(self, dstl_instance):
        dstl_instance.add_evidence("fact one", source={"draft": True})
        dstl_instance.add_evidence("fact two", source={"draft": False})

        results = dstl_instance.list_evidence(source_filter={"draft": True})
        assert len(results) == 1
        assert results[0].content == "fact one"

    def test_empty_store(self, dstl_instance):
        results = dstl_instance.list_evidence()
        assert results == []


class TestDistill:
    def test_creates_skill_file(self, dstl_instance, skills_dir):
        dstl_instance.add_evidence("Use type hints in Python")
        dstl_instance.add_evidence("Type hints improve code readability")

        path = dstl_instance.distill(topic="type hints", output_dir=skills_dir)
        assert os.path.exists(path)
        assert path.endswith(".md")

        content = open(path).read()
        assert "# type hints" in content.lower() or "# Type" in content

    def test_distill_all_evidence(self, dstl_instance, skills_dir):
        dstl_instance.add_evidence("fact one")
        dstl_instance.add_evidence("fact two")

        path = dstl_instance.distill(output_dir=skills_dir)
        assert os.path.exists(path)

    def test_distill_with_skill_id(self, dstl_instance, skills_dir):
        dstl_instance.add_evidence("some evidence")

        path = dstl_instance.distill(topic="testing", skill_id="my-skill", output_dir=skills_dir)
        assert "my-skill.md" in path

    def test_distill_no_evidence_raises(self, dstl_instance, skills_dir):
        with pytest.raises(ValueError, match="No evidence"):
            dstl_instance.distill(topic="nonexistent", output_dir=skills_dir)

    def test_custom_output_dir(self, dstl_instance, tmp_path):
        custom_dir = str(tmp_path / "custom" / "output")
        dstl_instance.add_evidence("some content")

        path = dstl_instance.distill(output_dir=custom_dir)
        assert os.path.exists(path)
        assert custom_dir in path

    def test_punctuation_only_title_gets_uuid_id(self, dstl_instance, skills_dir):
        dstl_instance.add_evidence("some evidence")

        path = dstl_instance.distill(topic="!!!", output_dir=skills_dir)
        filename = os.path.basename(path)
        assert filename != ".md"
        assert len(filename) > 3


class TestConsolidate:
    def test_creates_consolidated_file(self, dstl_instance, skills_dir):
        docs = [("repo-a", "- pattern one"), ("repo-b", "- pattern two")]
        path = dstl_instance.consolidate(docs, output_dir=skills_dir)
        assert os.path.exists(path)
        content = open(path).read()
        assert "repo-a" in content
        assert "repo-b" in content

    def test_with_topic(self, dstl_instance, skills_dir):
        docs = [("src", "- use type hints")]
        path = dstl_instance.consolidate(docs, topic="python guidelines", output_dir=skills_dir)
        assert os.path.exists(path)

    def test_with_skill_id(self, dstl_instance, skills_dir):
        docs = [("src", "- content")]
        path = dstl_instance.consolidate(docs, skill_id="my-consolidated", output_dir=skills_dir)
        assert "my-consolidated.md" in path

    def test_empty_documents_raises(self, dstl_instance, skills_dir):
        with pytest.raises(ValueError, match="No documents"):
            dstl_instance.consolidate([], output_dir=skills_dir)


class TestEditSkill:
    def _create_skill(self, dstl_instance, skills_dir):
        dstl_instance.add_evidence("original evidence")
        return dstl_instance.distill(
            topic="test topic", skill_id="test-skill", output_dir=skills_dir
        )

    def test_llm_edit(self, dstl_instance, skills_dir):
        self._create_skill(dstl_instance, skills_dir)

        path = dstl_instance.edit_skill("test-skill", instruction="add error handling section")
        content = open(path).read()
        assert "error handling" in content.lower()

    def test_manual_edit(self, dstl_instance, skills_dir):
        self._create_skill(dstl_instance, skills_dir)

        new_content = "# Updated Skill\n\nCompletely new content."
        path = dstl_instance.edit_skill("test-skill", content=new_content)
        assert open(path).read() == new_content

    def test_move_to_new_dir(self, dstl_instance, skills_dir, tmp_path):
        self._create_skill(dstl_instance, skills_dir)

        new_dir = str(tmp_path / "moved")
        path = dstl_instance.edit_skill("test-skill", content="moved content", output_dir=new_dir)
        assert new_dir in path
        assert os.path.exists(path)

    def test_edit_nonexistent_skill_raises(self, dstl_instance):
        with pytest.raises(ValueError, match="not found"):
            dstl_instance.edit_skill("nope", content="x")

    def test_edit_no_instruction_or_content_raises(self, dstl_instance, skills_dir):
        self._create_skill(dstl_instance, skills_dir)
        with pytest.raises(ValueError, match="instruction.*content"):
            dstl_instance.edit_skill("test-skill")


class TestReportOutcome:
    def test_returns_outcome_id(self, dstl_instance, skills_dir):
        dstl_instance.add_evidence("evidence")
        dstl_instance.distill(topic="test", skill_id="s1", output_dir=skills_dir)

        oid = dstl_instance.report_outcome("s1", success=True, notes="worked well")
        assert isinstance(oid, int)
        assert oid >= 1

    def test_nonexistent_skill_raises(self, dstl_instance):
        with pytest.raises(ValueError, match="not found"):
            dstl_instance.report_outcome("nope", success=True)
