import os

from fastapi import Depends, HTTPException

from chainlit.config import config
from chainlit.data import get_data_layer
from chainlit.logger import logger
from chainlit.oauth_providers import get_configured_oauth_providers

from .cookie import (
    OAuth2PasswordBearerWithCookie,
    clear_auth_cookie,
    get_token_from_cookies,
    set_auth_cookie,
)
from .jwt import create_jwt, decode_jwt, get_jwt_secret

reuseable_oauth = OAuth2PasswordBearerWithCookie(tokenUrl="/login", auto_error=False)


def ensure_jwt_secret():
    if require_login() and get_jwt_secret() is None:
        raise ValueError(
            "You must provide a JWT secret in the environment to use authentication. Run `chainlit create-secret` to generate one."
        )


def is_oauth_enabled():
    return config.code.oauth_callback and len(get_configured_oauth_providers()) > 0


def require_login():
    return (
        bool(os.environ.get("CHAINLIT_CUSTOM_AUTH"))
        or config.code.password_auth_callback is not None
        or config.code.header_auth_callback is not None
        or is_oauth_enabled()
    )


def get_configuration():
    return {
        "requireLogin": require_login(),
        "passwordAuth": config.code.password_auth_callback is not None,
        "headerAuth": config.code.header_auth_callback is not None,
        "oauthProviders": (
            get_configured_oauth_providers() if is_oauth_enabled() else []
        ),
        "default_theme": config.ui.default_theme,
    }


async def authenticate_user(token: str = Depends(reuseable_oauth)):
    try:
        user = decode_jwt(token)
    except Exception as e:
        raise HTTPException(
            status_code=401, detail="Invalid authentication token"
        ) from e

    if data_layer := get_data_layer():
        # Get or create persistent user if we've a data layer available.
        try:
            persisted_user = await data_layer.get_user(user.identifier)
            if persisted_user is None:
                persisted_user = await data_layer.create_user(user)
                assert persisted_user
        except Exception as e:
            logger.exception("Unable to get persisted_user from data layer: %s", e)
            return user

        if user and user.display_name:
            # Copy ephemeral display_name from authenticated user to persistent user.
            persisted_user.display_name = user.display_name

        return persisted_user

    return user


async def get_current_user(token: str = Depends(reuseable_oauth)):
    if not require_login():
        return None

    return await authenticate_user(token)


__all__ = [
    "clear_auth_cookie",
    "create_jwt",
    "get_configuration",
    "get_current_user",
    "get_token_from_cookies",
    "set_auth_cookie",
]
