"""
Platform-specific credential storage for second-opinion-mcp.

Backend selection:
- Windows: Credential Manager via keyring (DPAPI, secure)
- macOS GUI: Keychain via keyring (secure)
- macOS headless: File (~/.second-opinion-config)
- Linux: File (~/.second-opinion-config)

The file backend uses SSH-style security (plaintext + 600 permissions),
which provides the same security model as SSH private keys, AWS CLI,
and other command-line tools.
"""
import json
import logging
import os
import sys
from pathlib import Path
from typing import Literal

logger = logging.getLogger("second-opinion")

# Credential backend types
BackendType = Literal["keyring", "file"]

# File backend configuration
CONFIG_FILE_NAME = ".second-opinion-config"
KEYRING_SERVICE = "second-opinion"


def _is_macos_headless() -> bool:
    """Check if running on macOS without a GUI session."""
    # SSH session is headless
    if os.environ.get("SSH_CONNECTION") or os.environ.get("SSH_TTY"):
        return True

    # No DISPLAY and no Apple display server
    if not os.environ.get("DISPLAY") and not os.environ.get("__CFBundleIdentifier"):
        # Check if WindowServer is running (indicates GUI session)
        try:
            import subprocess
            result = subprocess.run(
                ["pgrep", "-x", "WindowServer"],
                capture_output=True,
                timeout=2
            )
            if result.returncode != 0:
                return True
        except Exception:
            logger.debug("WindowServer check failed", exc_info=True)

    return False


def detect_backend() -> tuple[BackendType, str]:
    """
    Detect the appropriate credential storage backend for this platform.

    Returns:
        Tuple of (backend_type, description) where backend_type is "keyring" or "file"
    """
    if sys.platform == "win32":
        return ("keyring", "Windows Credential Manager")

    if sys.platform == "darwin":
        if _is_macos_headless():
            return ("file", "macOS headless (file storage)")
        return ("keyring", "macOS Keychain")

    # Linux: always use file backend
    # This avoids D-Bus complexity and EncryptedKeyring password issues
    return ("file", "Linux file storage")


class FileCredentialBackend:
    """
    SSH-style file storage at ~/.second-opinion-config.

    Format: JSON with 600 permissions (owner read/write only).
    This is the same security model used by SSH private keys.
    """

    def __init__(self, config_path: Path | None = None):
        """Initialize with optional custom config path (for testing)."""
        if config_path:
            self._config_path = config_path
        else:
            self._config_path = Path.home() / CONFIG_FILE_NAME

    @property
    def config_path(self) -> Path:
        """Get the configuration file path."""
        return self._config_path

    def _ensure_secure_file(self) -> None:
        """Create config file with secure permissions if it doesn't exist."""
        if self._config_path.exists():
            # Verify permissions on existing file
            if sys.platform != "win32":
                mode = self._config_path.stat().st_mode & 0o777
                if mode != 0o600:
                    logger.warning(
                        "Fixing permissions on %s (was %o, setting to 600)",
                        self._config_path, mode
                    )
                    os.chmod(self._config_path, 0o600)
            return

        # Create new file with secure permissions
        self._config_path.parent.mkdir(parents=True, exist_ok=True)

        # Create empty config
        self._write_config({"credentials": {}})

    def _read_config(self) -> dict:
        """Read and parse the config file."""
        if not self._config_path.exists():
            return {"credentials": {}}

        try:
            content = self._config_path.read_text(encoding="utf-8")
            return json.loads(content) if content.strip() else {"credentials": {}}
        except (json.JSONDecodeError, OSError) as e:
            logger.error("Error reading config file %s (%s): %s", self._config_path, type(e).__name__, e)
            return {"credentials": {}}

    def _write_config(self, config: dict) -> None:
        """Write config to file with secure permissions."""
        # On Unix, use atomic write with secure permissions
        if sys.platform != "win32":
            # Create with restrictive umask
            old_umask = os.umask(0o077)
            try:
                self._config_path.write_text(
                    json.dumps(config, indent=2),
                    encoding="utf-8"
                )
                # Ensure permissions are correct
                os.chmod(self._config_path, 0o600)
            finally:
                os.umask(old_umask)
        else:
            # Windows doesn't have the same permission model
            self._config_path.write_text(
                json.dumps(config, indent=2),
                encoding="utf-8"
            )

    def get_password(self, service: str, name: str) -> str | None:
        """Get a stored credential."""
        config = self._read_config()
        credentials = config.get("credentials", {})
        return credentials.get(name)

    def set_password(self, service: str, name: str, password: str) -> None:
        """Store a credential."""
        self._ensure_secure_file()
        config = self._read_config()
        if "credentials" not in config:
            config["credentials"] = {}
        config["credentials"][name] = password
        self._write_config(config)
        logger.debug("Stored credential for %s", name)

    def delete_password(self, service: str, name: str) -> None:
        """Delete a stored credential."""
        config = self._read_config()
        credentials = config.get("credentials", {})
        if name in credentials:
            del credentials[name]
            self._write_config(config)
            logger.debug("Deleted credential for %s", name)


class KeyringCredentialBackend:
    """Wrapper around Python keyring for Windows/macOS."""

    def __init__(self):
        """Initialize keyring backend."""
        import keyring
        self._keyring = keyring

    def get_password(self, service: str, name: str) -> str | None:
        """Get a stored credential from system keyring."""
        try:
            return self._keyring.get_password(service, name)
        except Exception as e:
            logger.error("Keyring access failed: %s", e)
            return None

    def set_password(self, service: str, name: str, password: str) -> None:
        """Store a credential in system keyring."""
        self._keyring.set_password(service, name, password)
        logger.debug("Stored credential for %s in keyring", name)

    def delete_password(self, service: str, name: str) -> None:
        """Delete a credential from system keyring."""
        try:
            self._keyring.delete_password(service, name)
            logger.debug("Deleted credential for %s from keyring", name)
        except Exception as e:
            logger.debug("Could not delete keyring entry: %s", e)


class CredentialManager:
    """Manages credential storage with platform-specific backends.

    Singleton pattern: only one instance exists per process. First instantiation
    auto-detects the backend (keyring or file). Subsequent calls return the same
    instance without re-initializing.

    For testing, call CredentialManager.reset() in setup/teardown to clear the
    singleton and allow re-initialization with force_backend.

    Usage:
        manager = CredentialManager()
        api_key = manager.get("deepseek")
        manager.set("deepseek", "sk-xxxxx")
    """

    _instance: "CredentialManager | None" = None

    def __new__(cls, *args, **kwargs):
        """Singleton pattern."""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self, force_backend: BackendType | None = None):
        """
        Initialize credential manager.

        Args:
            force_backend: Force a specific backend (for testing)
        """
        if self._initialized and force_backend is None:
            return

        self._backend_type, self._backend_description = (
            (force_backend, f"forced {force_backend}")
            if force_backend
            else detect_backend()
        )

        if self._backend_type == "keyring":
            self._backend = KeyringCredentialBackend()
        else:
            self._backend = FileCredentialBackend()

        self._initialized = True
        logger.info("Credential backend: %s", self._backend_description)

    @classmethod
    def reset(cls) -> None:
        """Reset singleton instance (for testing)."""
        cls._instance = None

    @property
    def backend_type(self) -> BackendType:
        """Get the current backend type."""
        return self._backend_type

    @property
    def backend_description(self) -> str:
        """Get human-readable backend description."""
        return self._backend_description

    def get(self, provider: str) -> str | None:
        """Get API key for a provider."""
        return self._backend.get_password(KEYRING_SERVICE, provider)

    def set(self, provider: str, api_key: str) -> None:
        """Store API key for a provider."""
        self._backend.set_password(KEYRING_SERVICE, provider, api_key)

    def delete(self, provider: str) -> None:
        """Delete API key for a provider."""
        self._backend.delete_password(KEYRING_SERVICE, provider)

    def check_available(self) -> tuple[bool, str]:
        """
        Check if the credential backend is available and working.

        Returns:
            Tuple of (available, message)
        """
        try:
            # Test write/read/delete cycle
            test_key = "__test_connectivity__"
            test_value = "test"

            self._backend.set_password(KEYRING_SERVICE, test_key, test_value)
            result = self._backend.get_password(KEYRING_SERVICE, test_key)
            self._backend.delete_password(KEYRING_SERVICE, test_key)

            if result != test_value:
                return False, "Credential storage read/write failed"

            return True, self._backend_description

        except Exception as e:
            return False, f"Credential storage error: {e}"

    def get_all_configured(self, providers: list[str]) -> dict[str, bool]:
        """Check which providers have credentials configured."""
        result = {}
        for provider in providers:
            key = self.get(provider)
            result[provider] = bool(key)
        return result

    def get_config_section(self, section: str) -> dict | None:
        """Read a top-level section from the config file.

        Always reads from the file backend since config sections store
        structured objects (not simple strings suitable for keyring).
        """
        file_backend = self._backend
        if not isinstance(file_backend, FileCredentialBackend):
            file_backend = FileCredentialBackend()
        config = file_backend._read_config()
        data = config.get(section)
        return data if isinstance(data, dict) else None

    def set_config_section(self, section: str, data: dict) -> None:
        """Write a top-level section to the config file.

        Always writes to the file backend since config sections store
        structured objects (not simple strings suitable for keyring).
        """
        file_backend = self._backend
        if not isinstance(file_backend, FileCredentialBackend):
            file_backend = FileCredentialBackend()
        file_backend._ensure_secure_file()
        config = file_backend._read_config()
        config[section] = data
        file_backend._write_config(config)
