"""
Tests for the HallucinationChecker.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from hallucination_grep.checker import HallucinationChecker
from hallucination_grep.extractor import CodeReference, ReferenceExtractor, ReferenceKind
from hallucination_grep.indexer import CodebaseIndexer


def _make_ref(kind: ReferenceKind, name: str, line: int = 1, **kwargs) -> CodeReference:
    return CodeReference(
        kind=kind,
        name=name,
        line_number=line,
        raw_text=name,
        **kwargs,
    )


class TestHallucinationChecker:

    def _checker(self, tmp_codebase: Path) -> HallucinationChecker:
        index = CodebaseIndexer(str(tmp_codebase)).build()
        return HallucinationChecker(index, str(tmp_codebase))

    # ------------------------------------------------------------------
    # Function checks
    # ------------------------------------------------------------------

    def test_verifies_existing_function(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.FUNCTION, "get_user_data")
        result = checker.check([ref], "get_user_data()")
        assert result.verified_count == 1
        assert result.hallucination_count == 0

    def test_flags_nonexistent_function(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.FUNCTION, "get_user_profile")
        result = checker.check([ref], "get_user_profile()")
        assert result.hallucination_count == 1

    def test_suggests_similar_function(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.FUNCTION, "get_user_profile")
        result = checker.check([ref], "get_user_profile()")
        halluc = result.hallucinations[0]
        # difflib picks the closest match from the pool — either get_user_by_id
        # or get_user_data are acceptable suggestions (both share the get_user_ prefix)
        assert halluc.suggestion_name in ("get_user_by_id", "get_user_data")

    # ------------------------------------------------------------------
    # Class checks
    # ------------------------------------------------------------------

    def test_verifies_existing_class(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.CLASS, "UserManager")
        result = checker.check([ref], "UserManager")
        assert result.verified_count == 1

    def test_flags_nonexistent_class(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.CLASS, "DataProcessor")
        result = checker.check([ref], "DataProcessor")
        assert result.hallucination_count == 1

    def test_suggests_similar_class(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        # "UserHandler" is close to "UserManager"
        ref = _make_ref(ReferenceKind.CLASS, "UserHandler")
        result = checker.check([ref], "UserHandler")
        # May or may not find a suggestion depending on similarity threshold
        # Just check that it was flagged
        assert result.hallucination_count == 1

    # ------------------------------------------------------------------
    # File checks
    # ------------------------------------------------------------------

    def test_verifies_existing_file(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.FILE, "src/users.py")
        result = checker.check([ref], "src/users.py")
        assert result.verified_count == 1

    def test_flags_nonexistent_file(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.FILE, "src/helpers/formatter.py")
        result = checker.check([ref], "src/helpers/formatter.py")
        assert result.hallucination_count == 1

    def test_file_suggestion_for_similar_path(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.FILE, "src/helpers/formatter.py")
        result = checker.check([ref], "src/helpers/formatter.py")
        halluc = result.hallucinations[0]
        # Should suggest src/utils/format.py (similar name)
        assert halluc.suggestion_name is not None

    # ------------------------------------------------------------------
    # Import checks
    # ------------------------------------------------------------------

    def test_verifies_stdlib_import(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(
            ReferenceKind.IMPORT,
            "os.path",
            import_module="os",
            import_name="path",
        )
        result = checker.check([ref], "from os import path")
        assert result.verified_count == 1

    def test_flags_nonexistent_import(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(
            ReferenceKind.IMPORT,
            "config.DATABASE_URL",
            import_module="config",
            import_name="DATABASE_URL",
        )
        result = checker.check([ref], "from config import DATABASE_URL")
        # config module may or may not be indexed — either verified or hallucinated
        # but there should be exactly 1 result total
        assert result.total_references == 1

    # ------------------------------------------------------------------
    # Method checks
    # ------------------------------------------------------------------

    def test_verifies_existing_method(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.METHOD, "save")
        result = checker.check([ref], ".save()")
        assert result.verified_count == 1

    def test_flags_nonexistent_method(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        ref = _make_ref(ReferenceKind.METHOD, "process_and_save_everything")
        result = checker.check([ref], ".process_and_save_everything()")
        assert result.hallucination_count == 1

    # ------------------------------------------------------------------
    # Full end-to-end
    # ------------------------------------------------------------------

    def test_end_to_end_mixed_text(self, tmp_codebase: Path) -> None:
        llm_text = (
            "Use get_user_data() to fetch users.\n"
            "The UserManager class handles it.\n"
            "Call get_user_profile() which does not exist.\n"
            "See src/helpers/formatter.py for formatting.\n"
        )
        index = CodebaseIndexer(str(tmp_codebase)).build()
        extractor = ReferenceExtractor()
        references = extractor.extract(llm_text)
        checker = HallucinationChecker(index, str(tmp_codebase))
        report = checker.check(references, llm_text)

        verified_names = {r.reference.name for r in report.verified}
        halluc_names = {r.reference.name for r in report.hallucinations}

        assert "get_user_data" in verified_names
        assert "UserManager" in verified_names
        assert "get_user_profile" in halluc_names

    def test_hallucination_rate(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        refs = [
            _make_ref(ReferenceKind.FUNCTION, "get_user_data"),  # exists
            _make_ref(ReferenceKind.FUNCTION, "ghost_function"),  # not exists
        ]
        result = checker.check(refs, "")
        assert result.hallucination_rate == pytest.approx(0.5)

    def test_exit_code_reflects_hallucinations(self, tmp_codebase: Path) -> None:
        checker = self._checker(tmp_codebase)
        refs = [_make_ref(ReferenceKind.FUNCTION, "totally_fake_function")]
        result = checker.check(refs, "")
        assert result.hallucination_count > 0  # exit code 1 should be triggered
