# Copyright (C) 2022 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import os
from typing import Any, Dict, Optional
from aiocache import Cache
from ariadne.asgi import GraphQL
from starlette.applications import Starlette
from starlette.authentication import (
    AuthCredentials,
    AuthenticationBackend,
    UnauthenticatedUser,
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Mount, Route
from starlette.staticfiles import StaticFiles
from swh.auth.starlette.backends import BearerTokenAuthBackend
from swh.core import config
from swh.search import get_search as get_swh_search
from swh.search.interface import SearchInterface
from swh.storage import get_storage as get_swh_storage
from swh.storage.interface import StorageInterface
from .client.view import explorer_page
graphql_cfg: Dict[str, Any] = {}
storage: Optional[StorageInterface] = None
search: Optional[SearchInterface] = None
[docs]
def get_storage() -> StorageInterface:
    global storage
    if not storage:
        storage = get_swh_storage(**graphql_cfg["storage"])
    return storage 
[docs]
def get_search() -> SearchInterface:
    global search
    if not search:
        search = get_swh_search(**graphql_cfg["search"])
    return search 
[docs]
def get_config() -> Dict[str, Any]:
    global graphql_cfg
    if not graphql_cfg:
        config_path = os.environ.get("SWH_CONFIG_FILENAME")
        graphql_cfg = load_and_check_config(config_path)
    return graphql_cfg 
[docs]
class AnonymousAuthBackend(AuthenticationBackend):
[docs]
    async def authenticate(self, conn):
        return AuthCredentials(["anonymous"]), UnauthenticatedUser() 
 
[docs]
def load_and_check_config(config_path: Optional[str]) -> Dict[str, Any]:
    """Check the minimal configuration is set to run the api or raise an
       error explanation.
    Args:
        config_path: Path to the configuration file to load
    Raises:
        Error if the setup is not as expected
    Returns:
        configuration as a dict
    """
    if not config_path:
        raise EnvironmentError("Configuration file must be defined")
    if not os.path.exists(config_path):
        raise FileNotFoundError(f"Configuration file {config_path} does not exist")
    cfg = config.read(config_path)
    if "storage" not in cfg:
        raise KeyError("Missing 'storage' configuration")
    return cfg 
[docs]
def make_app_from_configfile():
    """Loading the configuration from a configuration file.
    SWH_CONFIG_FILENAME environment variable defines the
    configuration path to load.
    """
    from .app import schema, validation_rules
    from .errors import format_error, on_auth_error
    from .middlewares.logger import LogMiddleware
    graphql_cfg = get_config()
    ariadne_app = GraphQL(
        schema,
        debug=graphql_cfg["debug"],
        introspection=graphql_cfg["introspection"],
        validation_rules=validation_rules,
        error_formatter=format_error,
    )
    if "auth" in graphql_cfg:
        auth_backend = BearerTokenAuthBackend(
            server_url=graphql_cfg["auth"]["server"],
            realm_name=graphql_cfg["auth"]["realm"],
            client_id=graphql_cfg["auth"]["client"],
            # FIXME, improve this with response cache implementation
            cache=Cache.from_url(url=graphql_cfg["auth"]["cache"]["url"]),
        )
    else:
        auth_backend = AnonymousAuthBackend()
    middleware = [
        Middleware(
            CORSMiddleware,
            # FIXME, restrict origins after deploying the JS client
            allow_origins=["*"],
            allow_methods=("GET", "POST", "OPTIONS"),
            allow_headers=["*"],
        ),
        Middleware(
            AuthenticationMiddleware,
            backend=auth_backend,
            on_error=on_auth_error,
        ),
        Middleware(LogMiddleware),
    ]
    # Mount under a starlette application
    application = Starlette(
        routes=[
            Route("/", ariadne_app, methods=["POST"], name="graphql_api"),
            Route("/", explorer_page, methods=["GET"]),
            Mount(
                "/static",
                app=StaticFiles(packages=[("swh.graphql.client", "static")]),
                name="static",
            ),
        ],
        middleware=middleware,
    )
    return application