"""
Tests for the ReferenceExtractor.
"""

from __future__ import annotations

import pytest

from hallucination_grep.extractor import ReferenceExtractor, ReferenceKind


class TestReferenceExtractor:
    def _extract(self, text: str, checks=None):
        return ReferenceExtractor(checks=checks).extract(text)

    # ------------------------------------------------------------------
    # Function extraction
    # ------------------------------------------------------------------

    def test_extracts_function_call(self) -> None:
        refs = self._extract("Call get_user_profile() to fetch the user.")
        names = [r.name for r in refs if r.kind == ReferenceKind.FUNCTION]
        assert "get_user_profile" in names

    def test_extracts_multiple_functions(self) -> None:
        text = "Use fetch_data() then process_results() and finally save_output()."
        refs = self._extract(text)
        names = [r.name for r in refs if r.kind == ReferenceKind.FUNCTION]
        assert "fetch_data" in names
        assert "process_results" in names
        assert "save_output" in names

    def test_skips_builtin_functions(self) -> None:
        refs = self._extract("Call print() and len() on the list.")
        names = [r.name for r in refs if r.kind == ReferenceKind.FUNCTION]
        assert "print" not in names
        assert "len" not in names

    def test_extracts_function_line_number(self) -> None:
        text = "Line one.\nCall my_func() here.\nLine three."
        refs = self._extract(text)
        fn_refs = [r for r in refs if r.kind == ReferenceKind.FUNCTION and r.name == "my_func"]
        assert len(fn_refs) >= 1
        assert fn_refs[0].line_number == 2

    # ------------------------------------------------------------------
    # Class extraction
    # ------------------------------------------------------------------

    def test_extracts_pascal_case_class(self) -> None:
        refs = self._extract("Use UserManager to handle user operations.")
        names = [r.name for r in refs if r.kind == ReferenceKind.CLASS]
        assert "UserManager" in names

    def test_skips_single_word_class_names(self) -> None:
        # "User" alone is ambiguous — our regex requires at least two PascalCase segments
        refs = self._extract("The User object.")
        names = [r.name for r in refs if r.kind == ReferenceKind.CLASS]
        assert "User" not in names

    def test_skips_builtin_exception_classes(self) -> None:
        refs = self._extract("Raises ValueError if input is wrong.")
        names = [r.name for r in refs if r.kind == ReferenceKind.CLASS]
        assert "ValueError" not in names

    def test_skips_all_caps_acronyms(self) -> None:
        refs = self._extract("Send it via HTTP or HTTPS protocol.")
        names = [r.name for r in refs if r.kind == ReferenceKind.CLASS]
        assert "HTTP" not in names
        assert "HTTPS" not in names

    # ------------------------------------------------------------------
    # File extraction
    # ------------------------------------------------------------------

    def test_extracts_file_path(self) -> None:
        refs = self._extract("Edit src/helpers/formatter.py to fix the issue.")
        names = [r.name for r in refs if r.kind == ReferenceKind.FILE]
        assert any("formatter.py" in n for n in names)

    def test_extracts_file_in_backticks(self) -> None:
        refs = self._extract("Open `config/settings.json` for configuration.")
        names = [r.name for r in refs if r.kind == ReferenceKind.FILE]
        assert any("settings.json" in n for n in names)

    def test_extracts_dotslash_file(self) -> None:
        refs = self._extract("Run ./manage.py to start the server.")
        names = [r.name for r in refs if r.kind == ReferenceKind.FILE]
        assert any("manage.py" in n for n in names)

    # ------------------------------------------------------------------
    # Import extraction
    # ------------------------------------------------------------------

    def test_extracts_from_import(self) -> None:
        refs = self._extract("from config import DATABASE_URL")
        import_refs = [r for r in refs if r.kind == ReferenceKind.IMPORT]
        assert any(
            r.import_module == "config" and r.import_name == "DATABASE_URL"
            for r in import_refs
        )

    def test_extracts_bare_import(self) -> None:
        refs = self._extract("import my_custom_module")
        import_refs = [r for r in refs if r.kind == ReferenceKind.IMPORT]
        assert any(r.import_module == "my_custom_module" for r in import_refs)

    def test_extracts_multiname_from_import(self) -> None:
        refs = self._extract("from utils import format_response, parse_input")
        import_refs = [r for r in refs if r.kind == ReferenceKind.IMPORT]
        names = [r.import_name for r in import_refs]
        assert "format_response" in names
        assert "parse_input" in names

    # ------------------------------------------------------------------
    # Method extraction
    # ------------------------------------------------------------------

    def test_extracts_method_call(self) -> None:
        refs = self._extract("Call obj.process_data() to handle it.")
        names = [r.name for r in refs if r.kind == ReferenceKind.METHOD]
        assert "process_data" in names

    # ------------------------------------------------------------------
    # Variable extraction
    # ------------------------------------------------------------------

    def test_extracts_constant_in_backticks(self) -> None:
        refs = self._extract("Set `DATABASE_URL` to the correct value.")
        names = [r.name for r in refs if r.kind == ReferenceKind.VARIABLE]
        assert "DATABASE_URL" in names

    # ------------------------------------------------------------------
    # Deduplication
    # ------------------------------------------------------------------

    def test_deduplicates_references(self) -> None:
        text = "Call get_user_data() here and also get_user_data() there."
        refs = self._extract(text)
        fn_refs = [r for r in refs if r.kind == ReferenceKind.FUNCTION and r.name == "get_user_data"]
        assert len(fn_refs) == 1

    # ------------------------------------------------------------------
    # Check filtering
    # ------------------------------------------------------------------

    def test_check_filter_functions_only(self) -> None:
        text = "from utils import helper. Call my_func(). Use MyClass."
        refs = self._extract(text, checks=["functions"])
        kinds = {r.kind for r in refs}
        assert ReferenceKind.FUNCTION in kinds or len(refs) == 0
        assert ReferenceKind.IMPORT not in kinds

    def test_check_filter_imports_only(self) -> None:
        text = "from utils import helper. Call my_func()."
        refs = self._extract(text, checks=["imports"])
        kinds = {r.kind for r in refs}
        assert ReferenceKind.FUNCTION not in kinds
