"""Tests for auto-instrumentation hooks."""

import builtins
import sys
from unittest.mock import MagicMock, patch

import pytest

from risicare.instrumentation.hooks import (
    RisicareImportFinder,
    get_instrumented_modules,
    get_supported_modules,
    install_import_hooks,
    instrument_already_imported,
    is_instrumented,
    remove_import_hooks,
)


class TestImportHooks:
    """Tests for import hook installation and removal."""

    def setup_method(self):
        """Clean up hooks before each test."""
        remove_import_hooks()

    def teardown_method(self):
        """Clean up hooks after each test."""
        remove_import_hooks()

    def test_install_hooks(self):
        """Test installing import hooks."""
        original_import = builtins.__import__

        finder = install_import_hooks()

        assert finder is not None
        assert isinstance(finder, RisicareImportFinder)
        # Hooks work via builtins.__import__ replacement, not sys.meta_path
        assert builtins.__import__ is not original_import
        assert builtins.__import__.__name__ == "_instrumented_import"

    def test_install_hooks_idempotent(self):
        """Test that installing hooks twice returns same finder."""
        finder1 = install_import_hooks()
        finder2 = install_import_hooks()

        assert finder1 is finder2
        # Only one import replacement active
        assert builtins.__import__.__name__ == "_instrumented_import"

    def test_remove_hooks(self):
        """Test removing import hooks restores builtins.__import__."""
        original_import = builtins.__import__

        install_import_hooks()
        assert builtins.__import__.__name__ == "_instrumented_import"

        remove_import_hooks()
        assert builtins.__import__ is original_import

    def test_allowed_modules_filter(self):
        """Test that allowed_modules filters which modules are instrumented."""
        finder = install_import_hooks(allowed_modules={"openai"})

        assert "openai" in finder.allowed_modules
        assert "anthropic" not in finder.allowed_modules

    def test_get_supported_modules(self):
        """Test getting list of supported modules."""
        supported = get_supported_modules()

        # LLM providers
        assert "openai" in supported
        assert "anthropic" in supported
        assert "cohere" in supported
        assert "google.generativeai" in supported
        assert "mistralai" in supported

        # Agent frameworks (wired to external integration packages)
        assert "langgraph" in supported
        assert "crewai" in supported
        assert "autogen" in supported

    def test_get_instrumented_modules_empty(self):
        """Test getting instrumented modules when no supported modules are pre-imported."""
        supported = get_supported_modules()
        # Temporarily remove any already-imported supported modules so that
        # instrument_already_imported() (called inside install_import_hooks) finds nothing.
        saved = {mod: sys.modules.pop(mod) for mod in supported if mod in sys.modules}
        try:
            install_import_hooks()
            instrumented = get_instrumented_modules()
            assert len(instrumented) == 0
        finally:
            sys.modules.update(saved)

    def test_is_instrumented_false(self):
        """Test is_instrumented returns False for non-instrumented modules."""
        install_import_hooks()

        assert is_instrumented("openai") is False
        assert is_instrumented("anthropic") is False


class TestFinder:
    """Tests for RisicareImportFinder tracker."""

    def test_default_allowed_modules(self):
        """Test finder includes all supported modules by default."""
        finder = RisicareImportFinder()
        supported = get_supported_modules()
        assert finder.allowed_modules == supported

    def test_custom_allowed_modules(self):
        """Test finder respects custom allowed_modules."""
        finder = RisicareImportFinder(allowed_modules={"openai"})
        assert finder.allowed_modules == {"openai"}
        assert "anthropic" not in finder.allowed_modules

    def test_instrumented_tracking(self):
        """Test _instrumented set tracks modules."""
        finder = RisicareImportFinder()
        assert len(finder._instrumented) == 0
        finder._instrumented.add("openai")
        assert "openai" in finder._instrumented
        assert len(finder._instrumented) == 1

    def test_instrumented_initially_empty(self):
        """Test _instrumented is empty on creation."""
        finder = RisicareImportFinder()
        assert len(finder._instrumented) == 0


class TestInstrumentAlreadyImported:
    """Tests for instrumenting already-imported modules."""

    def setup_method(self):
        """Clean up hooks before each test."""
        remove_import_hooks()

    def teardown_method(self):
        """Clean up hooks after each test."""
        remove_import_hooks()

    def test_instrument_already_imported_none(self):
        """Test instrumenting when no supported modules imported."""
        # Make sure no LLM modules are imported
        to_check = ["openai", "anthropic", "cohere", "mistralai"]
        for mod in to_check:
            if mod in sys.modules:
                del sys.modules[mod]

        install_import_hooks()
        count = instrument_already_imported()

        # Should not have instrumented anything
        assert count == 0

    @patch("risicare.instrumentation.hooks._load_instrumentor")
    def test_instrument_already_imported_with_module(self, mock_load):
        """Test instrumenting already-imported module."""
        # Mock an already-imported module
        mock_module = MagicMock()
        mock_instrumentor = MagicMock()
        mock_load.return_value = mock_instrumentor

        with patch.dict(sys.modules, {"openai": mock_module}):
            install_import_hooks()
            count = instrument_already_imported()

            # Should have called the instrumentor
            if count > 0:
                mock_instrumentor.assert_called_once_with(mock_module)


class TestEnvironmentAutoStart:
    """Tests for environment-based auto-instrumentation."""

    def teardown_method(self):
        """Clean up hooks after each test."""
        remove_import_hooks()

    @patch.dict("os.environ", {"RISICARE_TRACING": "true"})
    def test_auto_start_on_import(self):
        """Test that RISICARE_TRACING=true installs hooks."""
        # Clear any existing hooks
        remove_import_hooks()

        # Re-import the module to trigger auto-start
        import importlib
        import risicare.instrumentation

        importlib.reload(risicare.instrumentation)

        # Hooks should be installed (builtins.__import__ replaced)
        assert builtins.__import__.__name__ == "_instrumented_import"

    @patch.dict("os.environ", {"RISICARE_TRACING": "false"})
    def test_no_auto_start_when_disabled(self):
        """Test that RISICARE_TRACING=false doesn't install hooks."""
        # Clear any existing hooks
        remove_import_hooks()

        # Re-import the module
        import importlib
        import risicare.instrumentation

        importlib.reload(risicare.instrumentation)

        # Hooks should not be installed — builtins.__import__ unchanged
        assert builtins.__import__.__name__ != "_instrumented_import"
