"""Tests for platform-specific credential storage."""
import json
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock

import pytest

from second_opinion_mcp.credentials import (
    detect_backend,
    FileCredentialBackend,
    KeyringCredentialBackend,
    CredentialManager,
    _is_macos_headless,
)


class TestDetectBackend:
    """Tests for backend detection logic."""

    def test_windows_uses_keyring(self):
        """Windows should use keyring (Credential Manager)."""
        with patch.object(sys, 'platform', 'win32'):
            backend_type, desc = detect_backend()
            assert backend_type == "keyring"
            assert "Windows" in desc

    def test_macos_gui_uses_keyring(self):
        """macOS with GUI should use keyring (Keychain)."""
        with patch.object(sys, 'platform', 'darwin'):
            with patch('second_opinion_mcp.credentials._is_macos_headless', return_value=False):
                backend_type, desc = detect_backend()
                assert backend_type == "keyring"
                assert "macOS Keychain" in desc

    def test_macos_headless_uses_file(self):
        """macOS in headless mode should use file backend."""
        with patch.object(sys, 'platform', 'darwin'):
            with patch('second_opinion_mcp.credentials._is_macos_headless', return_value=True):
                backend_type, desc = detect_backend()
                assert backend_type == "file"
                assert "headless" in desc.lower()

    def test_linux_uses_file(self):
        """Linux should always use file backend."""
        with patch.object(sys, 'platform', 'linux'):
            backend_type, desc = detect_backend()
            assert backend_type == "file"
            assert "Linux" in desc


class TestMacOSHeadlessDetection:
    """Tests for macOS headless detection."""

    def test_ssh_connection_is_headless(self):
        """SSH connection should be detected as headless."""
        with patch.dict(os.environ, {'SSH_CONNECTION': '192.168.1.1 12345 192.168.1.2 22'}):
            assert _is_macos_headless() is True

    def test_ssh_tty_is_headless(self):
        """SSH TTY should be detected as headless."""
        with patch.dict(os.environ, {'SSH_TTY': '/dev/pts/0'}, clear=False):
            # Clear SSH_CONNECTION to test SSH_TTY alone
            env = dict(os.environ)
            env.pop('SSH_CONNECTION', None)
            env['SSH_TTY'] = '/dev/pts/0'
            with patch.dict(os.environ, env, clear=True):
                assert _is_macos_headless() is True


class TestFileCredentialBackend:
    """Tests for file-based credential storage."""

    def test_create_config_file(self):
        """Should create config file with correct permissions."""
        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"
            backend = FileCredentialBackend(config_path)

            backend.set_password("service", "test-provider", "test-key")

            assert config_path.exists()

            # Check permissions on Unix
            if sys.platform != "win32":
                mode = config_path.stat().st_mode & 0o777
                assert mode == 0o600, f"Expected 600, got {oct(mode)}"

    def test_store_and_retrieve(self):
        """Should store and retrieve credentials."""
        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"
            backend = FileCredentialBackend(config_path)

            backend.set_password("service", "deepseek", "sk-test123")
            result = backend.get_password("service", "deepseek")

            assert result == "sk-test123"

    def test_retrieve_nonexistent(self):
        """Should return None for nonexistent credentials."""
        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"
            backend = FileCredentialBackend(config_path)

            result = backend.get_password("service", "nonexistent")
            assert result is None

    def test_delete_credential(self):
        """Should delete credentials."""
        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"
            backend = FileCredentialBackend(config_path)

            backend.set_password("service", "test", "value")
            assert backend.get_password("service", "test") == "value"

            backend.delete_password("service", "test")
            assert backend.get_password("service", "test") is None

    def test_multiple_providers(self):
        """Should handle multiple providers."""
        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"
            backend = FileCredentialBackend(config_path)

            backend.set_password("service", "deepseek", "key1")
            backend.set_password("service", "moonshot", "key2")
            backend.set_password("service", "openrouter", "key3")

            assert backend.get_password("service", "deepseek") == "key1"
            assert backend.get_password("service", "moonshot") == "key2"
            assert backend.get_password("service", "openrouter") == "key3"

    def test_update_credential(self):
        """Should update existing credentials."""
        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"
            backend = FileCredentialBackend(config_path)

            backend.set_password("service", "test", "old-value")
            backend.set_password("service", "test", "new-value")

            assert backend.get_password("service", "test") == "new-value"

    def test_config_file_format(self):
        """Config file should be valid JSON with expected structure."""
        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"
            backend = FileCredentialBackend(config_path)

            backend.set_password("service", "test", "value")

            content = config_path.read_text()
            data = json.loads(content)

            assert "credentials" in data
            assert data["credentials"]["test"] == "value"

    def test_fix_permissions_on_existing_file(self):
        """Should fix permissions if file exists with wrong permissions."""
        if sys.platform == "win32":
            pytest.skip("Permission test not applicable on Windows")

        with tempfile.TemporaryDirectory() as tmpdir:
            config_path = Path(tmpdir) / ".second-opinion-config"

            # Create file with wrong permissions
            config_path.write_text('{"credentials": {}}')
            os.chmod(config_path, 0o644)

            backend = FileCredentialBackend(config_path)
            backend.set_password("service", "test", "value")

            # Permissions should be fixed
            mode = config_path.stat().st_mode & 0o777
            assert mode == 0o600


class TestCredentialManager:
    """Tests for the unified credential manager."""

    def setup_method(self):
        """Reset singleton before each test."""
        CredentialManager.reset()

    def teardown_method(self):
        """Reset singleton after each test."""
        CredentialManager.reset()

    def test_singleton_pattern(self):
        """Should return same instance on multiple calls."""
        manager1 = CredentialManager(force_backend="file")
        manager2 = CredentialManager()

        assert manager1 is manager2

    def test_force_file_backend(self):
        """Should use file backend when forced."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Reset to allow new instance
            CredentialManager.reset()

            manager = CredentialManager(force_backend="file")
            assert manager.backend_type == "file"

    def test_get_and_set(self):
        """Should get and set credentials."""
        with tempfile.TemporaryDirectory() as tmpdir:
            CredentialManager.reset()

            # Patch the config path
            with patch.object(
                FileCredentialBackend,
                '__init__',
                lambda self, path=None: setattr(self, '_config_path', Path(tmpdir) / ".config") or None
            ):
                manager = CredentialManager(force_backend="file")
                manager._backend._config_path = Path(tmpdir) / ".config"

                manager.set("test-provider", "test-key")
                result = manager.get("test-provider")

                assert result == "test-key"

    def test_check_available(self):
        """Should verify backend is working."""
        with tempfile.TemporaryDirectory() as tmpdir:
            CredentialManager.reset()

            manager = CredentialManager(force_backend="file")
            manager._backend._config_path = Path(tmpdir) / ".config"

            available, message = manager.check_available()
            assert available is True

    def test_get_all_configured(self):
        """Should check multiple providers at once."""
        with tempfile.TemporaryDirectory() as tmpdir:
            CredentialManager.reset()

            # Use a clean config file
            config_path = Path(tmpdir) / ".config"
            manager = CredentialManager(force_backend="file")
            manager._backend._config_path = config_path

            # Ensure clean state
            if config_path.exists():
                config_path.unlink()

            manager.set("deepseek", "key1")
            manager.set("moonshot", "key2")

            result = manager.get_all_configured(["deepseek", "moonshot", "openrouter"])

            assert result["deepseek"] is True
            assert result["moonshot"] is True
            assert result["openrouter"] is False


class TestKeyringCredentialBackend:
    """Tests for keyring-based credential storage."""

    def test_get_password_calls_keyring(self):
        """Should call keyring.get_password."""
        mock_keyring = MagicMock()
        mock_keyring.get_password.return_value = "test-key"

        backend = KeyringCredentialBackend()
        backend._keyring = mock_keyring

        result = backend.get_password("service", "name")

        mock_keyring.get_password.assert_called_once_with("service", "name")
        assert result == "test-key"

    def test_set_password_calls_keyring(self):
        """Should call keyring.set_password."""
        mock_keyring = MagicMock()

        backend = KeyringCredentialBackend()
        backend._keyring = mock_keyring

        backend.set_password("service", "name", "value")

        mock_keyring.set_password.assert_called_once_with("service", "name", "value")

    def test_delete_password_calls_keyring(self):
        """Should call keyring.delete_password."""
        mock_keyring = MagicMock()

        backend = KeyringCredentialBackend()
        backend._keyring = mock_keyring

        backend.delete_password("service", "name")

        mock_keyring.delete_password.assert_called_once_with("service", "name")

    def test_get_password_handles_error(self):
        """Should return None on keyring error."""
        mock_keyring = MagicMock()
        mock_keyring.get_password.side_effect = Exception("Keyring error")

        backend = KeyringCredentialBackend()
        backend._keyring = mock_keyring

        result = backend.get_password("service", "name")
        assert result is None


