"""
Credential provider with resolution chain.

Resolution order:
1. CLI flags (passed directly, not stored)
2. Environment variables (first-class for CI)
3. OS keychain (default for humans - macOS/Windows)
4. Legacy plaintext files (read-only fallback)
5. External secret manager plugins (M2)
"""

from __future__ import annotations

from typing import Dict, List, Optional, Protocol, runtime_checkable

from g_gremlin.credentials.models import (
    INTEGRATION_REGISTRY,
    IntegrationSpec,
    IntegrationStatus,
    SecretSource,
    SecretValue,
)


class CredentialProviderError(Exception):
    """Base exception for credential provider errors."""
    pass


class SecretNotFoundError(CredentialProviderError):
    """Secret does not exist in any backend."""
    pass


class BackendUnavailableError(CredentialProviderError):
    """Backend is not available on this platform."""
    pass


@runtime_checkable
class CredentialBackend(Protocol):
    """
    Protocol for credential storage backends.

    Backends are responsible for storing/retrieving secrets from a single source.
    """

    @property
    def source(self) -> SecretSource:
        """The source type this backend represents."""
        ...

    @property
    def is_available(self) -> bool:
        """Whether this backend is usable on the current platform."""
        ...

    @property
    def is_read_only(self) -> bool:
        """Whether this backend only supports reading (e.g., legacy files)."""
        ...

    def get_secret(
        self,
        integration: str,
        key: str,
        profile: str = "default",
    ) -> Optional[SecretValue]:
        """Retrieve a secret. Returns None if not found in this backend."""
        ...

    def set_secret(
        self,
        integration: str,
        key: str,
        value: str,
        profile: str = "default",
    ) -> None:
        """Store a secret. Raises if backend is read-only."""
        ...

    def delete_secret(
        self,
        integration: str,
        key: str,
        profile: str = "default",
    ) -> bool:
        """Delete a secret. Returns True if deleted, False if not found."""
        ...

    def list_secrets(
        self,
        integration: Optional[str] = None,
        profile: str = "default",
    ) -> List[tuple]:
        """List (integration, key) pairs available in this backend."""
        ...


class CredentialProvider:
    """
    Main credential provider with resolution chain.

    Resolution order:
    1. CLI flags (passed directly, not stored)
    2. Environment variables
    3. OS keychain (default for humans)
    4. Legacy file fallback
    5. Plugin backends (M2)
    """

    def __init__(
        self,
        backends: Optional[List[CredentialBackend]] = None,
        cli_overrides: Optional[Dict[str, str]] = None,
    ):
        """
        Initialize credential provider.

        Args:
            backends: Custom list of backends. If None, uses default chain.
            cli_overrides: Dict of "integration.key" -> value for CLI flag overrides.
        """
        self._cli_overrides = cli_overrides or {}
        self._backends: List[CredentialBackend] = []
        self._backends_initialized = False

        if backends is not None:
            self._backends = backends
            self._backends_initialized = True

    def _ensure_backends(self) -> None:
        """Lazy-initialize default backends on first use."""
        if self._backends_initialized:
            return

        self._backends = self._init_default_backends()
        self._backends_initialized = True

    def _init_default_backends(self) -> List[CredentialBackend]:
        """
        Initialize backends in priority order (lazy imports).

        All backend imports are deferred to avoid loading keyring/etc on startup.
        """
        backends: List[CredentialBackend] = []

        # EnvVar backend is always available, no external deps
        from g_gremlin.credentials.backends.env_var import EnvVarBackend
        backends.append(EnvVarBackend())

        # Try keychain backends based on platform
        import platform
        system = platform.system()

        if system == "Darwin":
            try:
                from g_gremlin.credentials.backends.keychain import MacOSKeychainBackend
                backend = MacOSKeychainBackend()
                if backend.is_available:
                    backends.append(backend)
            except ImportError:
                pass  # keyring not installed
        elif system == "Windows":
            try:
                from g_gremlin.credentials.backends.windows_cred import WindowsCredentialBackend
                backend = WindowsCredentialBackend()
                if backend.is_available:
                    backends.append(backend)
            except ImportError:
                pass  # keyring not installed

        # Legacy file backend is always available (read-only after migration)
        from g_gremlin.credentials.backends.legacy_file import LegacyFileBackend
        backends.append(LegacyFileBackend())

        return backends

    def get_secret(
        self,
        integration: str,
        key: str,
        profile: str = "default",
    ) -> SecretValue:
        """
        Get a secret using the resolution chain.

        Raises:
            SecretNotFoundError: If secret not found in any backend.
        """
        # Check CLI overrides first
        cli_key = f"{integration}.{key}"
        if cli_key in self._cli_overrides:
            return SecretValue(
                value=self._cli_overrides[cli_key],
                source=SecretSource.CLI_FLAG,
                integration=integration,
                key=key,
                profile=profile,
            )

        # Initialize backends lazily
        self._ensure_backends()

        # Try each backend in order
        for backend in self._backends:
            if not backend.is_available:
                continue
            secret = backend.get_secret(integration, key, profile)
            if secret is not None:
                return secret

        raise SecretNotFoundError(
            f"Secret '{integration}.{key}' not found in any backend. "
            f"Set via environment variable or run: g-gremlin auth set {integration}"
        )

    def get_secret_or_none(
        self,
        integration: str,
        key: str,
        profile: str = "default",
    ) -> Optional[SecretValue]:
        """Get a secret, returning None if not found (no exception)."""
        try:
            return self.get_secret(integration, key, profile)
        except SecretNotFoundError:
            return None

    def set_secret(
        self,
        integration: str,
        key: str,
        value: str,
        profile: str = "default",
    ) -> SecretSource:
        """
        Store a secret in the first writable backend.

        Returns the source where it was stored.

        Raises:
            CredentialProviderError: If no writable backend available.
        """
        self._ensure_backends()

        for backend in self._backends:
            if not backend.is_available or backend.is_read_only:
                continue
            backend.set_secret(integration, key, value, profile)
            return backend.source

        raise CredentialProviderError(
            "No writable credential backend available. "
            "Keyring is installed but no OS credential store was detected."
        )

    def delete_secret(
        self,
        integration: str,
        key: str,
        profile: str = "default",
    ) -> bool:
        """Delete a secret from all writable backends."""
        self._ensure_backends()

        deleted = False
        for backend in self._backends:
            if not backend.is_available or backend.is_read_only:
                continue
            if backend.delete_secret(integration, key, profile):
                deleted = True
        return deleted

    def list_integrations(
        self,
        profile: str = "default",
    ) -> List[IntegrationStatus]:
        """
        List all known integrations and their configuration status.
        """
        self._ensure_backends()

        statuses = []
        for spec in INTEGRATION_REGISTRY.values():
            status = self._get_integration_status(spec, profile)
            statuses.append(status)
        return statuses

    def list_configured_integrations(self, profile: str = "default") -> List[str]:
        """
        Return integration names that have at least one configured key.
        """
        return [s.integration for s in self.list_integrations(profile) if s.configured]

    def _get_integration_status(
        self,
        spec: IntegrationSpec,
        profile: str,
    ) -> IntegrationStatus:
        """Check status of a single integration."""
        keys_present: List[str] = []
        keys_missing: List[str] = []
        source = SecretSource.NOT_FOUND
        is_plaintext = False

        for key in spec.required_keys:
            secret = self.get_secret_or_none(spec.name, key, profile)
            if secret is not None:
                keys_present.append(key)
                source = secret.source
                if secret.source == SecretSource.LEGACY_FILE:
                    is_plaintext = True
            else:
                keys_missing.append(key)

        for key in spec.optional_keys:
            secret = self.get_secret_or_none(spec.name, key, profile)
            if secret is not None:
                keys_present.append(key)
                if secret.source == SecretSource.LEGACY_FILE:
                    is_plaintext = True

        return IntegrationStatus(
            integration=spec.name,
            configured=len(keys_present) > 0,
            source=source,
            profile=profile,
            keys_present=keys_present,
            keys_missing=keys_missing,
            is_plaintext=is_plaintext,
        )

    def has_keychain_backend(self) -> bool:
        """Check if a keychain backend is available."""
        self._ensure_backends()
        for backend in self._backends:
            if backend.source == SecretSource.KEYCHAIN and backend.is_available:
                return True
        return False

    def get_plaintext_secrets(self, profile: str = "default") -> List[tuple]:
        """
        Get all secrets stored in plaintext (for migration).

        Returns list of (integration, key, value).
        """
        self._ensure_backends()

        for backend in self._backends:
            if backend.source == SecretSource.LEGACY_FILE and backend.is_available:
                # Legacy backend has get_migratable_secrets method
                if hasattr(backend, "get_migratable_secrets"):
                    return backend.get_migratable_secrets()
        return []
