"""
Tests for the check-absolute-module-paths pre-commit hook.

Ensures the hook correctly detects absolute module paths (local_deep_research.*)
that should be relative imports, while allowing legitimate uses.
"""

import ast
import json
import sys
from importlib import import_module
from pathlib import Path


# Add the pre-commit hooks directory to path
HOOKS_DIR = Path(__file__).parent.parent.parent / ".pre-commit-hooks"
sys.path.insert(0, str(HOOKS_DIR))

# Import the checker from the hook (must be after sys.path modification)
hook_module = import_module("check-absolute-module-paths")  # noqa: E402
AbsoluteModulePathChecker = hook_module.AbsoluteModulePathChecker
LEGITIMATE_ABSOLUTE_REFS = hook_module.LEGITIMATE_ABSOLUTE_REFS


class TestAbsoluteModulePathChecker:
    """Tests for the AbsoluteModulePathChecker AST visitor."""

    def _check_code(self, code: str, filename: str = "src/module.py") -> list:
        """Helper to check code and return errors."""
        tree = ast.parse(code)
        checker = AbsoluteModulePathChecker(filename)
        checker.visit(tree)
        return checker.errors

    # --- Detection tests ---

    def test_detects_web_search_engine_absolute_path(self):
        """Should detect absolute paths in the web_search_engines subpackage."""
        code = """
module_path = "local_deep_research.web_search_engines.engines.search_engine_brave"
"""
        errors = self._check_code(code)
        assert len(errors) == 1
        assert "local_deep_research.web_search_engines" in errors[0][1]

    def test_detects_llm_absolute_path(self):
        """Should detect absolute paths in the llm subpackage."""
        code = """
path = "local_deep_research.llm.providers.implementations.openai_provider"
"""
        errors = self._check_code(code)
        assert len(errors) == 1
        assert "local_deep_research.llm" in errors[0][1]

    def test_detects_arbitrary_subpackage_absolute_path(self):
        """Should detect absolute paths in any subpackage."""
        code = """
x = "local_deep_research.some_new_package.foo.bar"
"""
        errors = self._check_code(code)
        assert len(errors) == 1

    def test_detects_multiple_violations(self):
        """Should detect multiple violations in one file."""
        code = """
a = "local_deep_research.web_search_engines.engines.local_embedding_manager"
b = "local_deep_research.web_search_engines.engines.search_engine_brave"
c = "local_deep_research.llm.providers.implementations.openai_provider"
"""
        errors = self._check_code(code)
        assert len(errors) == 3

    def test_reports_correct_line_numbers(self):
        """Should report the correct line number for violations."""
        code = """
# Some comment
x = 1

module_path = "local_deep_research.web_search_engines.engines.foo"
"""
        errors = self._check_code(code)
        assert len(errors) == 1
        assert errors[0][0] == 5  # Line 5

    # --- Allowance tests ---

    def test_allows_relative_paths(self):
        """Should not flag relative module paths."""
        code = """
module_path = ".engines.search_engine_brave"
"""
        errors = self._check_code(code)
        assert len(errors) == 0

    def test_allows_legitimate_absolute_refs(self):
        """Should not flag known legitimate absolute references."""
        for ref in LEGITIMATE_ABSOLUTE_REFS:
            code = f'''
x = "{ref}"
'''
            errors = self._check_code(code)
            assert len(errors) == 0, f"Legitimate ref '{ref}' was flagged"

    def test_allows_unrelated_strings(self):
        """Should not flag strings that don't start with local_deep_research."""
        code = """
name = "some_other_package.module"
url = "https://example.com"
msg = "This is a message"
"""
        errors = self._check_code(code)
        assert len(errors) == 0

    def test_allows_bare_package_name(self):
        """Should not flag 'local_deep_research' without a dot-suffix."""
        code = """
name = "local_deep_research"
"""
        errors = self._check_code(code)
        assert len(errors) == 0

    def test_allows_non_string_constants(self):
        """Should not flag integer or other non-string constants."""
        code = """
x = 42
y = 3.14
z = True
"""
        errors = self._check_code(code)
        assert len(errors) == 0


class TestIsFileAllowed:
    """Tests for the _is_file_allowed() function."""

    def test_allows_test_directory(self):
        """Files under tests/ should be allowed."""
        assert hook_module._is_file_allowed("tests/test_something.py") is True
        assert (
            hook_module._is_file_allowed("tests/security/test_whitelist.py")
            is True
        )

    def test_allows_pre_commit_hooks_directory(self):
        """Files under .pre-commit-hooks/ should be allowed."""
        assert (
            hook_module._is_file_allowed(
                ".pre-commit-hooks/check-absolute-module-paths.py"
            )
            is True
        )

    def test_allows_module_whitelist(self):
        """module_whitelist.py should be allowed."""
        assert (
            hook_module._is_file_allowed(
                "src/local_deep_research/security/module_whitelist.py"
            )
            is True
        )

    def test_allows_test_prefix_basename(self):
        """Files with test_ prefix should be allowed."""
        assert hook_module._is_file_allowed("src/test_something.py") is True

    def test_allows_test_suffix_basename(self):
        """Files with _test.py suffix should be allowed."""
        assert hook_module._is_file_allowed("src/something_test.py") is True

    def test_rejects_normal_source_file(self):
        """Normal source files should NOT be allowed."""
        assert (
            hook_module._is_file_allowed("src/local_deep_research/config.py")
            is False
        )

    def test_no_false_positive_on_tests_in_filename(self):
        """'tests' in a filename (not dir) should not allow the file."""
        # 'my_tests_helper.py' has 'tests' in the name but isn't a test file
        # and isn't in a tests/ directory
        assert hook_module._is_file_allowed("src/my_tests_helper.py") is False

    def test_no_false_positive_on_module_whitelist_substring(self):
        """module_whitelist_extra.py should NOT match module_whitelist.py."""
        assert (
            hook_module._is_file_allowed(
                "src/security/module_whitelist_extra.py"
            )
            is False
        )

    def test_no_false_positive_on_test_in_middle_of_name(self):
        """'contest_results.py' should not match test_ prefix."""
        assert hook_module._is_file_allowed("src/contest_results.py") is False


class TestCheckPythonFile:
    """Integration tests for check_python_file()."""

    def test_clean_file_passes(self, tmp_path):
        """A file with only relative paths should pass."""
        clean = tmp_path / "clean_module.py"
        clean.write_text("""
config = {
    "module_path": ".engines.search_engine_brave",
    "class_name": "BraveSearchEngine",
}
""")
        assert hook_module.check_python_file(str(clean)) is True

    def test_violation_file_fails(self, tmp_path):
        """A file with absolute paths should fail."""
        bad = tmp_path / "bad_module.py"
        bad.write_text("""
config = {
    "module_path": "local_deep_research.web_search_engines.engines.search_engine_brave",
    "class_name": "BraveSearchEngine",
}
""")
        assert hook_module.check_python_file(str(bad)) is False

    def test_legitimate_ref_passes(self, tmp_path):
        """A file using a legitimate absolute ref should pass."""
        legit = tmp_path / "legit_module.py"
        legit.write_text("""
from importlib import resources
data_dir = resources.files("local_deep_research.defaults.settings")
""")
        assert hook_module.check_python_file(str(legit)) is True

    def test_syntax_error_passes(self, tmp_path):
        """Files with syntax errors should pass (let other tools handle them)."""
        broken = tmp_path / "broken.py"
        broken.write_text("def bad(:\n    pass")
        assert hook_module.check_python_file(str(broken)) is True

    def test_test_file_always_passes(self, tmp_path):
        """Test files should always pass even with absolute paths."""
        test_file = tmp_path / "test_whitelist.py"
        test_file.write_text("""
path = "local_deep_research.web_search_engines.engines.foo"
""")
        assert hook_module.check_python_file(str(test_file)) is True

    def test_nonexistent_file_fails(self):
        """A non-existent file should fail (returns False)."""
        assert (
            hook_module.check_python_file("/nonexistent/path/file.py") is False
        )


class TestCheckJsonFile:
    """Integration tests for check_json_file()."""

    def test_clean_json_passes(self, tmp_path):
        """A JSON file with relative module_path should pass."""
        clean = tmp_path / "engine.json"
        clean.write_text(
            json.dumps(
                {
                    "module_path": ".engines.search_engine_brave",
                    "class_name": "BraveSearchEngine",
                }
            )
        )
        assert hook_module.check_json_file(str(clean)) is True

    def test_absolute_module_path_in_json_fails(self, tmp_path):
        """A JSON file with absolute module_path should fail."""
        bad = tmp_path / "bad_engine.json"
        bad.write_text(
            json.dumps(
                {
                    "module_path": "local_deep_research.web_search_engines.engines.search_engine_brave",
                    "class_name": "BraveSearchEngine",
                }
            )
        )
        assert hook_module.check_json_file(str(bad)) is False

    def test_nested_module_path_in_json_fails(self, tmp_path):
        """A nested JSON module_path with absolute path should fail."""
        bad = tmp_path / "nested.json"
        bad.write_text(
            json.dumps(
                {
                    "engines": {
                        "brave": {
                            "module_path": "local_deep_research.web_search_engines.engines.search_engine_brave",
                            "class_name": "BraveSearchEngine",
                        }
                    }
                }
            )
        )
        assert hook_module.check_json_file(str(bad)) is False

    def test_absolute_path_in_non_module_path_key_passes(self, tmp_path):
        """Absolute paths in non-module_path keys should NOT trigger (no false positive)."""
        ok = tmp_path / "description.json"
        ok.write_text(
            json.dumps(
                {
                    "description": "Uses local_deep_research.web_search_engines.engines.foo internally",
                    "module_path": ".engines.search_engine_brave",
                }
            )
        )
        assert hook_module.check_json_file(str(ok)) is True

    def test_invalid_json_fails(self, tmp_path):
        """Invalid JSON should fail gracefully (returns False)."""
        bad = tmp_path / "broken.json"
        bad.write_text("{invalid json}")
        assert hook_module.check_json_file(str(bad)) is False

    def test_legitimate_ref_in_module_path_passes(self, tmp_path):
        """Legitimate absolute refs in module_path should pass."""
        legit = tmp_path / "legit.json"
        legit.write_text(
            json.dumps(
                {"module_path": "local_deep_research.web_search_engines"}
            )
        )
        assert hook_module.check_json_file(str(legit)) is True

    def test_absolute_full_search_module_in_json_fails(self, tmp_path):
        """A JSON file with absolute full_search_module should fail."""
        bad = tmp_path / "bad_full_search.json"
        bad.write_text(
            json.dumps(
                {
                    "full_search_module": "local_deep_research.web_search_engines.engines.full_search",
                    "full_search_class": "FullSearchResults",
                }
            )
        )
        assert hook_module.check_json_file(str(bad)) is False

    def test_relative_full_search_module_in_json_passes(self, tmp_path):
        """A JSON file with relative full_search_module should pass."""
        ok = tmp_path / "ok_full_search.json"
        ok.write_text(
            json.dumps(
                {
                    "full_search_module": ".engines.full_search",
                    "full_search_class": "FullSearchResults",
                }
            )
        )
        assert hook_module.check_json_file(str(ok)) is True

    def test_module_path_in_list_of_dicts(self, tmp_path):
        """Should detect absolute paths in lists of engine configs."""
        bad = tmp_path / "engines_list.json"
        bad.write_text(
            json.dumps(
                [
                    {
                        "module_path": ".engines.good",
                        "class_name": "Good",
                    },
                    {
                        "module_path": "local_deep_research.web_search_engines.engines.bad",
                        "class_name": "Bad",
                    },
                ]
            )
        )
        assert hook_module.check_json_file(str(bad)) is False
