"""FastAPI authentication middleware for Supabase JWT verification."""

import os
import logging
from typing import Optional
from functools import lru_cache

import httpx
import jwt
from fastapi import Request, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

from .models import AuthenticatedUser
from .exceptions import AuthError, InvalidTokenError, ExpiredTokenError, MissingTokenError

logger = logging.getLogger(__name__)

# Security scheme for OpenAPI docs
security = HTTPBearer(auto_error=False)


class SupabaseAuth:
    """
    Supabase JWT verification using JWKS.
    
    Usage:
        auth = SupabaseAuth(
            supabase_url="https://xxx.supabase.co",
            supabase_anon_key="your-anon-key",  # Optional, for audience validation
        )
        
        @app.get("/protected")
        async def protected(user: AuthenticatedUser = Depends(auth.get_user)):
            return {"user_id": user.sub}
    """
    
    def __init__(
        self,
        supabase_url: Optional[str] = None,
        supabase_anon_key: Optional[str] = None,
        skip_verification_in_dev: bool = True,
    ):
        self.supabase_url = supabase_url or os.getenv("SUPABASE_URL")
        self.supabase_anon_key = supabase_anon_key or os.getenv("SUPABASE_ANON_KEY")
        self.skip_verification_in_dev = skip_verification_in_dev
        self._jwks_client: Optional[jwt.PyJWKClient] = None
        
        if not self.supabase_url:
            raise ValueError("SUPABASE_URL must be set")
    
    @property
    def jwks_url(self) -> str:
        """JWKS endpoint for Supabase project."""
        return f"{self.supabase_url}/auth/v1/.well-known/jwks.json"
    
    @property
    def issuer(self) -> str:
        """Expected JWT issuer."""
        return f"{self.supabase_url}/auth/v1"
    
    def _get_jwks_client(self) -> jwt.PyJWKClient:
        """Get or create JWKS client (cached)."""
        if self._jwks_client is None:
            self._jwks_client = jwt.PyJWKClient(
                self.jwks_url,
                cache_keys=True,
                lifespan=3600,  # Cache keys for 1 hour
            )
        return self._jwks_client
    
    def verify_token(self, token: str) -> AuthenticatedUser:
        """
        Verify a Supabase JWT and return user info.
        
        Raises:
            InvalidTokenError: Token is malformed or signature invalid
            ExpiredTokenError: Token has expired
        """
        try:
            # Get signing key from JWKS
            jwks_client = self._get_jwks_client()
            signing_key = jwks_client.get_signing_key_from_jwt(token)
            
            # Decode and verify
            payload = jwt.decode(
                token,
                signing_key.key,
                algorithms=["RS256"],
                issuer=self.issuer,
                options={
                    "verify_aud": False,  # Supabase doesn't always set aud
                    "verify_iss": True,
                    "verify_exp": True,
                },
            )
            
            return AuthenticatedUser(
                sub=payload["sub"],
                email=payload.get("email"),
                email_verified=payload.get("email_verified", False),
                raw_claims=payload,
            )
            
        except jwt.ExpiredSignatureError:
            raise ExpiredTokenError()
        except jwt.InvalidTokenError as e:
            logger.warning(f"Invalid token: {e}")
            raise InvalidTokenError(str(e))
        except Exception as e:
            logger.error(f"Token verification failed: {e}")
            raise InvalidTokenError("Token verification failed")
    
    async def get_user(
        self,
        request: Request,
        credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    ) -> AuthenticatedUser:
        """
        FastAPI dependency to get authenticated user.
        
        Usage:
            @app.get("/me")
            async def get_me(user: AuthenticatedUser = Depends(auth.get_user)):
                return {"id": user.sub}
        """
        # Skip auth in local development
        if self.skip_verification_in_dev and os.getenv("ENV") == "local":
            return AuthenticatedUser(
                sub="local-dev-user",
                email="dev@local",
                email_verified=True,
            )
        
        # Extract token
        token = None
        if credentials:
            token = credentials.credentials
        
        # Fallback: check x-user-id header (legacy support)
        if not token:
            legacy_uid = request.headers.get("x-user-id")
            if legacy_uid:
                logger.warning("Using legacy x-user-id header - migrate to Bearer token")
                return AuthenticatedUser(sub=legacy_uid)
        
        if not token:
            raise HTTPException(status_code=401, detail="Missing authorization")
        
        try:
            return self.verify_token(token)
        except AuthError as e:
            raise HTTPException(status_code=e.status_code, detail=e.message)
    
    async def get_user_optional(
        self,
        request: Request,
        credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    ) -> Optional[AuthenticatedUser]:
        """
        FastAPI dependency that returns None instead of raising if not authenticated.
        Useful for endpoints that work differently for logged-in vs anonymous users.
        """
        try:
            return await self.get_user(request, credentials)
        except HTTPException:
            return None


# Convenience: module-level instance using environment variables
_default_auth: Optional[SupabaseAuth] = None


def _get_default_auth() -> SupabaseAuth:
    """Get or create default auth instance from environment."""
    global _default_auth
    if _default_auth is None:
        _default_auth = SupabaseAuth()
    return _default_auth


async def get_current_user(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
) -> AuthenticatedUser:
    """
    Convenience dependency using environment-configured auth.
    
    Usage:
        from pargo_auth import get_current_user, AuthenticatedUser
        
        @app.get("/me")
        async def get_me(user: AuthenticatedUser = Depends(get_current_user)):
            return {"id": user.sub}
    """
    auth = _get_default_auth()
    return await auth.get_user(request, credentials)


def require_auth():
    """
    Dependency that can be used in router dependencies list.
    
    Usage:
        router = APIRouter(dependencies=[Depends(require_auth())])
    """
    return Depends(get_current_user)
