"""
Clerk authentication adapter.

Implements AuthPort using Clerk's JWKS endpoint for JWT verification.
"""

from typing import Any, Dict, List, Optional

import jwt
from jwt import PyJWKClient

from ..config.auth_config import AuthConfig
from ..ports.auth_port import AuthPort
from ..exceptions.auth_exceptions import TokenError


class ClerkAdapter(AuthPort):
    """
    Clerk-based authentication adapter.

    Verifies JWTs against Clerk's JWKS endpoint and extracts
    role/organization claims from the token payload.
    """

    def __init__(self, config: AuthConfig):
        self._config = config
        self._jwks_client = PyJWKClient(config.jwks_url)

    def _decode_token(self, token: str) -> Dict[str, Any]:
        """Shared decode logic for sync and async paths."""
        try:
            signing_key = self._jwks_client.get_signing_key_from_jwt(token)

            decode_options = {"verify_audience": False}
            kwargs: Dict[str, Any] = {
                "jwt": token,
                "key": signing_key.key,
                "algorithms": self._config.algorithms,
                "options": decode_options,
            }

            if self._config.issuer:
                kwargs["issuer"] = self._config.issuer
            if self._config.audience:
                kwargs["audience"] = self._config.audience
                decode_options["verify_audience"] = True

            return jwt.decode(**kwargs)
        except jwt.ExpiredSignatureError:
            raise TokenError("Token has expired")
        except jwt.InvalidTokenError as e:
            raise TokenError(f"Invalid token: {str(e)}")
        except Exception as e:
            raise TokenError(f"Token verification failed: {str(e)}")

    def verify_token(self, token: str) -> Dict[str, Any]:
        return self._decode_token(token)

    async def verify_token_async(self, token: str) -> Dict[str, Any]:
        # PyJWKClient doesn't have native async; delegate to sync.
        # For high-throughput, consider caching the JWKS keys.
        return self._decode_token(token)

    def has_role(self, payload: Dict[str, Any], roles: List[str]) -> bool:
        # Clerk puts custom roles in public_metadata.rol when no org is configured,
        # and in org_role when the user is scoped to an organization.
        # Check both locations so the same decorator works either way.
        org_role = payload.get("org_role")
        if org_role and org_role in roles:
            return True

        metadata = payload.get("public_metadata") or payload.get("metadata") or {}
        user_rol = metadata.get("rol", "")
        return user_rol in roles

    def get_org_id(self, payload: Dict[str, Any]) -> Optional[str]:
        return payload.get("org_id")

    def get_user_id(self, payload: Dict[str, Any]) -> Optional[str]:
        return payload.get("sub")
