"""
Abstract base for user OAuth storage providers.

This module defines the interface that all user OAuth credential
storage backends must implement, along with capability detection.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

from empowernow_common.vault.user_oauth.types import UserOAuthCredential


@dataclass
class UserOAuthProviderCapabilities:
    """
    Describes capabilities of a user OAuth storage provider.

    Different backends may have different capabilities. This dataclass
    allows consuming code to check what operations are supported.

    Attributes:
        supports_refresh_tracking: Can track refresh timestamps and failure counts
        supports_metadata: Can store arbitrary metadata with credentials
        supports_multi_tenant: Supports tenant isolation
        supports_list_credentials: Can list all credentials for a user
        supports_atomic_upsert: Store operations are atomic insert-or-update
        supports_batch_fetch: Can fetch multiple credentials in one call
        persistence_type: "persistent" (database), "cache" (Redis), or "memory"
    """

    supports_refresh_tracking: bool = True
    supports_metadata: bool = True
    supports_multi_tenant: bool = True
    supports_list_credentials: bool = True
    supports_atomic_upsert: bool = True
    supports_batch_fetch: bool = False
    persistence_type: str = "persistent"


class BaseUserOAuthProvider(ABC):
    """
    Abstract base class for user OAuth token storage.

    Implementations handle the actual storage, encryption, and retrieval
    of user-bound OAuth credentials. This base class defines the contract
    that all providers must fulfill.

    The provider is responsible for:
    - Encrypting tokens before storage
    - Decrypting tokens on retrieval
    - Managing credential lifecycle (status, refresh tracking)
    - Tenant isolation when applicable

    Example:
        class MyProvider(BaseUserOAuthProvider):
            PROVIDER_TYPE = "my_storage"

            async def get_credential(self, user_arn, system_key, tenant_id=None):
                # Implementation here
                pass
    """

    PROVIDER_TYPE: str = "base"
    CAPABILITIES = UserOAuthProviderCapabilities()

    @abstractmethod
    async def get_credential(
        self,
        user_arn: str,
        system_key: str,
        tenant_id: Optional[str] = None,
    ) -> Optional[UserOAuthCredential]:
        """
        Get a user's OAuth credential for a specific system.

        Args:
            user_arn: ARN identifying the user
            system_key: Identifier for the external system (e.g., "salesforce")
            tenant_id: Optional tenant ID for multi-tenant isolation

        Returns:
            UserOAuthCredential if found, None otherwise

        Raises:
            CredentialDecryptionError: If decryption fails
        """
        pass

    @abstractmethod
    async def store_credential(
        self,
        user_arn: str,
        system_key: str,
        tokens: Dict[str, Any],
        metadata: Optional[Dict[str, Any]] = None,
        tenant_id: Optional[str] = None,
    ) -> str:
        """
        Store or update a user's OAuth credential.

        This is an upsert operation - creates if not exists, updates if exists.

        Args:
            user_arn: ARN identifying the user
            system_key: Identifier for the external system
            tokens: OAuth token response containing at minimum:
                - access_token: The access token string
                - token_type: Usually "Bearer"
                - expires_in: Seconds until expiration (optional)
                - refresh_token: Refresh token (optional)
                - scope: Space-separated scopes (optional)
            metadata: Optional additional data to store (e.g., instance_url)
            tenant_id: Optional tenant ID for multi-tenant isolation

        Returns:
            Credential ID (UUID string)

        Raises:
            CredentialStorageError: If storage fails
        """
        pass

    @abstractmethod
    async def delete_credential(
        self,
        user_arn: str,
        system_key: str,
        tenant_id: Optional[str] = None,
    ) -> bool:
        """
        Delete a user's credential.

        Args:
            user_arn: ARN identifying the user
            system_key: Identifier for the external system
            tenant_id: Optional tenant ID for multi-tenant isolation

        Returns:
            True if credential was deleted, False if not found
        """
        pass

    @abstractmethod
    async def list_credentials(
        self,
        user_arn: str,
        tenant_id: Optional[str] = None,
    ) -> List[Dict[str, Any]]:
        """
        List all credentials for a user (metadata only, no tokens).

        Args:
            user_arn: ARN identifying the user
            tenant_id: Optional tenant ID for multi-tenant isolation

        Returns:
            List of credential metadata dictionaries containing:
                - system_key: The system identifier
                - status: Current status
                - system_user_email: Email in external system (if known)
                - scopes: List of granted scopes
                - last_used_at: ISO timestamp of last use
                - created_at: ISO timestamp of creation
        """
        pass

    @abstractmethod
    async def mark_needs_reauth(
        self,
        user_arn: str,
        system_key: str,
        tenant_id: Optional[str] = None,
    ) -> None:
        """
        Mark a credential as needing re-authorization.

        Called when refresh fails permanently (e.g., token revoked).
        The user must complete a new OAuth flow to restore access.

        Args:
            user_arn: ARN identifying the user
            system_key: Identifier for the external system
            tenant_id: Optional tenant ID for multi-tenant isolation
        """
        pass

    @abstractmethod
    async def increment_refresh_failure(
        self,
        user_arn: str,
        system_key: str,
        tenant_id: Optional[str] = None,
    ) -> int:
        """
        Increment the refresh failure count.

        Called after a failed refresh attempt. May trigger needs_reauth
        status if failure count exceeds threshold.

        Args:
            user_arn: ARN identifying the user
            system_key: Identifier for the external system
            tenant_id: Optional tenant ID for multi-tenant isolation

        Returns:
            New failure count after increment
        """
        pass

    async def reset_refresh_failures(
        self,
        user_arn: str,
        system_key: str,
        tenant_id: Optional[str] = None,
    ) -> None:
        """
        Reset the refresh failure count after successful refresh.

        Default implementation re-stores with reset count.
        Providers may override for more efficient updates.

        Args:
            user_arn: ARN identifying the user
            system_key: Identifier for the external system
            tenant_id: Optional tenant ID for multi-tenant isolation
        """
        # Default: providers should override with direct update
        pass

    async def get_credentials_batch(
        self,
        user_arn: str,
        system_keys: List[str],
        tenant_id: Optional[str] = None,
    ) -> Dict[str, Optional[UserOAuthCredential]]:
        """
        Fetch multiple credentials in a single operation.

        Default implementation calls get_credential for each key.
        Providers with batch support should override.

        Args:
            user_arn: ARN identifying the user
            system_keys: List of system identifiers to fetch
            tenant_id: Optional tenant ID for multi-tenant isolation

        Returns:
            Dict mapping system_key to credential (or None if not found)
        """
        result = {}
        for system_key in system_keys:
            result[system_key] = await self.get_credential(
                user_arn, system_key, tenant_id
            )
        return result
