"""
Public API Authentication

This module provides the APITokenAuthentication class for authenticating
requests using API tokens in the Authorization header.

Usage:
    Authorization: Bearer ss_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
"""

from django.utils import timezone
from rest_framework import authentication, exceptions


class APITokenAuthentication(authentication.BaseAuthentication):
    """
    Custom authentication class for API tokens.

    Authenticates requests using Bearer tokens in the Authorization header.
    The token must be a valid, active API token created for a business.

    On successful authentication, the request will have:
    - request.auth: The APIToken instance
    - request.user: An AnonymousUser (API tokens are not tied to users)
    - request.api_token: The APIToken instance (alias for convenience)
    - request.tenant: The tenant associated with the token

    Example:
        Authorization: Bearer ss_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

    Raises:
        AuthenticationFailed: If the token is invalid, expired, or inactive
    """

    keyword = 'Bearer'

    def authenticate(self, request):
        """
        Authenticate the request and return a tuple of (user, token).

        Returns:
            tuple: (None, APIToken) if authentication succeeds
            None: If no Bearer token is provided (allow other auth methods)

        Raises:
            AuthenticationFailed: If token is invalid/expired/inactive
        """
        auth_header = authentication.get_authorization_header(request)

        if not auth_header:
            return None

        try:
            auth_parts = auth_header.decode('utf-8').split()
        except UnicodeDecodeError:
            raise exceptions.AuthenticationFailed(
                detail='Invalid token header. Token string should not contain invalid characters.',
                code='authentication_error'
            )

        if not auth_parts:
            return None

        if auth_parts[0].lower() != self.keyword.lower():
            # Not a Bearer token, let other authentication methods handle it
            return None

        if len(auth_parts) == 1:
            raise exceptions.AuthenticationFailed(
                detail='Invalid token header. No credentials provided.',
                code='authentication_error'
            )

        if len(auth_parts) > 2:
            raise exceptions.AuthenticationFailed(
                detail='Invalid token header. Token string should not contain spaces.',
                code='authentication_error'
            )

        token_key = auth_parts[1]
        return self.authenticate_token(token_key, request)

    def authenticate_token(self, key, request):
        """
        Authenticate using the token key.

        Args:
            key: The full API token string
            request: The HTTP request object

        Returns:
            tuple: (None, APIToken) on success

        Raises:
            AuthenticationFailed: If token is invalid
        """
        # Import here to avoid circular imports
        from .models import APIToken

        # Validate token format
        if not key.startswith('ss_live_') and not key.startswith('ss_test_'):
            raise exceptions.AuthenticationFailed(
                detail='Invalid API token format.',
                code='authentication_error'
            )

        # Look up the token
        token = APIToken.get_by_key(key)

        if token is None:
            raise exceptions.AuthenticationFailed(
                detail='Invalid API token.',
                code='authentication_error'
            )

        if not token.is_active:
            raise exceptions.AuthenticationFailed(
                detail='API token has been revoked.',
                code='authentication_error'
            )

        if token.is_expired():
            raise exceptions.AuthenticationFailed(
                detail='API token has expired.',
                code='authentication_error'
            )

        # Update last used timestamp (async to not slow down requests)
        self._update_last_used(token)

        # Attach useful attributes to the request
        request.api_token = token
        request.tenant = token.tenant

        # Return (user, auth) - user is None for API tokens
        return (None, token)

    def authenticate_header(self, request):
        """
        Return the WWW-Authenticate header value for 401 responses.
        """
        return f'{self.keyword} realm="api"'

    def _update_last_used(self, token):
        """
        Update the token's last_used_at timestamp.

        We do this in a fire-and-forget manner to not slow down the request.
        In a production environment, you might want to batch these updates
        or use a background task.
        """
        # Simple synchronous update for now
        # Could be optimized with a background task in production
        try:
            token.last_used_at = timezone.now()
            token.save(update_fields=['last_used_at'])
        except Exception:
            # Don't fail the request if we can't update the timestamp
            pass


class OptionalAPITokenAuthentication(APITokenAuthentication):
    """
    Like APITokenAuthentication but doesn't require authentication.

    Use this for endpoints that can optionally accept API token auth
    but also support other authentication methods or anonymous access.
    """

    def authenticate(self, request):
        """
        Authenticate if a Bearer token is provided, otherwise return None.
        """
        auth_header = authentication.get_authorization_header(request)

        if not auth_header:
            return None

        try:
            auth_parts = auth_header.decode('utf-8').split()
        except UnicodeDecodeError:
            return None

        if not auth_parts or auth_parts[0].lower() != self.keyword.lower():
            return None

        if len(auth_parts) != 2:
            return None

        token_key = auth_parts[1]

        # Don't raise exceptions, just return None if invalid
        try:
            return self.authenticate_token(token_key, request)
        except exceptions.AuthenticationFailed:
            return None
