import logging

import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jwt import (
    ExpiredSignatureError,
    InvalidAudienceError,
    InvalidIssuerError,
    InvalidTokenError,
    PyJWKClient,
)
from pydantic import BaseModel, ConfigDict, ValidationError

from app.core.config import get_settings

logger = logging.getLogger(__name__)
bearer_scheme = HTTPBearer(auto_error=False)


class TokenPayload(BaseModel):
    """Validated token payload extracted from Supabase JWT."""

    model_config = ConfigDict(extra="ignore")

    sub: str | None = None
    user_id: str | None = None
    aud: str | list[str] | None = None
    exp: int | None = None

    @property
    def resolved_user_id(self) -> str | None:
        """Return the user id derived from the payload."""
        return self.user_id or self.sub


def decode_jwt_token(token: str) -> TokenPayload:
    """Decode and validate a Supabase JWT token.

    Args:
        token: Encoded JWT string.

    Returns:
        TokenPayload: Validated payload with normalized claims.

    Raises:
        HTTPException: When the token is invalid or expired.
    """
    settings = get_settings()
    header: dict = {}
    try:
        header = jwt.get_unverified_header(token)
    except Exception:
        header = {}

    try:
        alg = str(header.get("alg") or "HS256")
        # Supabase may issue asymmetric JWTs (e.g. ES256) and exposes public keys via JWKS.
        # Use the shared secret only for HS* algorithms.
        if not alg.startswith("HS"):
            # Context7 (Supabase docs): verify Supabase JWTs via the project's JWKS:
            # https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json
            jwks_url = f"{settings.supabase_jwt_issuer.rstrip('/')}/.well-known/jwks.json"
            jwk_client = PyJWKClient(jwks_url)
            signing_key = jwk_client.get_signing_key_from_jwt(token)
            payload = jwt.decode(
                token,
                signing_key.key,
                algorithms=[alg],
                audience=settings.supabase_jwt_audience,
                issuer=settings.supabase_jwt_issuer,
            )
        else:
            payload = jwt.decode(
                token,
                settings.supabase_jwt_secret,
                algorithms=[alg],
                audience=settings.supabase_jwt_audience,
                issuer=settings.supabase_jwt_issuer,
            )
    except ExpiredSignatureError as exc:
        logger.info("JWT expired", extra={"error": "expired_signature"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token expired",
        ) from exc
    except InvalidAudienceError as exc:
        logger.info("JWT audience invalid", extra={"error": "invalid_audience"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token audience",
        ) from exc
    except InvalidIssuerError as exc:
        logger.info("JWT issuer invalid", extra={"error": "invalid_issuer"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token issuer",
        ) from exc
    except InvalidTokenError as exc:
        logger.info("JWT invalid", extra={"error": "invalid_token"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
        ) from exc
    except Exception as exc:
        # Defensive: crypto/key parsing issues should not bubble as 500s.
        logger.info("JWT invalid (unexpected)", extra={"error": "invalid_token_unexpected"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
        ) from exc

    try:
        return TokenPayload.model_validate(payload)
    except ValidationError as exc:
        logger.info("JWT payload invalid", extra={"error": "payload_validation"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token payload",
        ) from exc


def verify_supabase_jwt(
    credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
) -> str:
    """Verify Supabase JWT and return the resolved user id.

    Args:
        credentials: Parsed authorization header credentials.

    Returns:
        str: Supabase user id.

    Raises:
        HTTPException: When authorization header is missing or token invalid.
    """
    if credentials is None or credentials.scheme.lower() != "bearer":
        logger.info("JWT missing", extra={"error": "missing_bearer"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Not authenticated",
        )

    payload = decode_jwt_token(credentials.credentials)
    user_id = payload.resolved_user_id
    if not user_id:
        logger.info("JWT missing user id", extra={"error": "missing_user_id"})
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token payload",
        )

    return user_id
