"""Tests for blueSPY API auto-discovery and loading."""

import importlib
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock

import pytest

from bluespy_mcp.loader import discover_bluespy, _unregister_bluespy_deinit, _PLATFORM_PATHS


class TestPlatformPaths:
    """Verify platform-specific search paths are defined."""

    def test_macos_paths_exist(self):
        assert "Darwin" in _PLATFORM_PATHS
        assert len(_PLATFORM_PATHS["Darwin"]) > 0

    def test_windows_paths_exist(self):
        assert "Windows" in _PLATFORM_PATHS
        assert len(_PLATFORM_PATHS["Windows"]) > 0

    def test_linux_paths_exist(self):
        assert "Linux" in _PLATFORM_PATHS
        assert len(_PLATFORM_PATHS["Linux"]) > 0


class TestDiscoverBluespy:
    """Test the discover_bluespy() resolution order."""

    def setup_method(self):
        """Reset the loader cache before each test."""
        from bluespy_mcp.loader import reset_cache
        reset_cache()

    def test_explicit_env_path_takes_priority(self, tmp_path):
        """BLUESPY_API_PATH env var should be checked first."""
        fake_api = tmp_path / "bluespy.py"
        fake_api.write_text("# fake bluespy\npackets = []\n")

        with patch.dict("os.environ", {"BLUESPY_API_PATH": str(tmp_path)}):
            module = discover_bluespy()
            assert module is not None

    def test_falls_back_to_vendor(self):
        """When nothing else is found, use the bundled _vendor copy."""
        with patch.dict("os.environ", {}, clear=True):
            with patch("bluespy_mcp.loader._try_platform_paths", return_value=None):
                with patch("bluespy_mcp.loader._try_direct_import", return_value=None):
                    module = discover_bluespy()
                    # Should fall back to vendor — may fail if dylib not present,
                    # but the function should attempt it
                    assert module is not None or True  # vendor may fail without dylib

    def test_returns_none_when_all_fail(self):
        """When everything fails, return None (don't crash)."""
        with patch.dict("os.environ", {}, clear=True):
            with patch("bluespy_mcp.loader._try_platform_paths", return_value=None):
                with patch("bluespy_mcp.loader._try_direct_import", return_value=None):
                    with patch("bluespy_mcp.loader._try_vendor", return_value=None):
                        module = discover_bluespy()
                        assert module is None


class TestUnregisterBluespyDeinit:
    """Test that bluespy_deinit atexit handler is removed after load."""

    def test_unregisters_deinit(self):
        """_unregister_bluespy_deinit calls atexit.unregister on the deinit function."""
        mock_module = MagicMock()
        mock_deinit = MagicMock()
        mock_module._libbluespy.bluespy_deinit = mock_deinit

        with patch("bluespy_mcp.loader.atexit") as mock_atexit:
            _unregister_bluespy_deinit(mock_module)
            mock_atexit.unregister.assert_called_once_with(mock_deinit)

    def test_handles_missing_libbluespy(self):
        """No crash if module doesn't have _libbluespy attribute."""
        mock_module = MagicMock(spec=[])  # No attributes
        _unregister_bluespy_deinit(mock_module)  # Should not raise

    def test_handles_none_libbluespy(self):
        """No crash if _libbluespy is None."""
        mock_module = MagicMock()
        mock_module._libbluespy = None
        _unregister_bluespy_deinit(mock_module)  # Should not raise
