# Copyright (c) 2022, TU Wien
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
import os

from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
from tornado import web
from traitlets import Dict, Float, List, Unicode, config, default

GRADER_COOKIE_NAME = "grader_service_login"


class GraderServer(config.LoggingConfigurable, web.Application):
    """As an unmanage jupyter hub service, the application gets these
    environment variables from the hub

    See jupyterhub docs section on launching-a-hub-managed-service:
    https://jupyterhub.readthedocs.io/en/stable/reference/services.html#launching-a-hub-managed-service
    """

    cookie_max_age_days = Float(
        14,
        help="""Number of days for a login cookie to be valid.
        Default is two weeks.
        """,
    ).tag(config=True)

    static_file_path = Unicode(
        os.path.join(os.path.dirname(__file__), "static"),
        help="""Absolute path to the static file directory.""",
    ).tag(config=True)

    logo_file = Unicode(
        "", help="Specify path to a logo image to override the Grader logo in the banner."
    ).tag(config=True)

    @default("logo_file")
    def _logo_file_default(self):
        return os.path.join(os.path.dirname(__file__), "static", "images", "grader_logo.png")

    template_paths = List(
        help="Paths to search for jinja templates, before using the default templates."
    ).tag(config=True)

    @default("template_paths")
    def _template_paths_default(self):
        return [os.path.join(os.path.dirname(__file__), "templates")]

    jinja_environment_options = Dict(
        help="Supply extra arguments that will be passed to Jinja environment."
    ).tag(config=True)

    template_vars = Dict(
        help="""Extra variables to be passed into jinja templates.

        Values in dict may contain callable objects.
        If value is callable, the current user is passed as argument.

        Example::

            def callable_value(user):
                # user is generated by handlers.base.get_current_user
                with open("/tmp/file.txt", "r") as f:
                    ret = f.read()
                ret = ret.replace("<username>", user.name)
                return ret

            c.JupyterHub.template_vars = {
                "key1": "value1",
                "key2": callable_value,
            }
        """
    ).tag(config=True)

    def __init__(
        self,
        grader_service_dir: str,
        base_url: str,
        authenticator,
        oauth_provider,
        session_maker,
        **kwargs,
    ):
        kwargs.update(dict(static_path=self.static_file_path))
        super().__init__(**kwargs)
        self.grader_service_dir = grader_service_dir
        self.base_url = base_url
        self.authenticator = authenticator
        self.oauth_provider = oauth_provider
        self.cookie_name = GRADER_COOKIE_NAME
        self.session_maker = session_maker

        jinja_options = dict(autoescape=True, enable_async=True)
        jinja_options.update(self.jinja_environment_options)
        base_path = self._template_paths_default()[0]
        if base_path not in self.template_paths:
            self.template_paths.append(base_path)
        loader = ChoiceLoader(
            [
                PrefixLoader({"templates": FileSystemLoader([base_path])}, "/"),
                FileSystemLoader(self.template_paths),
            ]
        )
        self.jinja_env = Environment(loader=loader, **jinja_options)
        # We need a sync jinja environment too, for the times we *must* use sync
        # code - particularly in RequestHandler.write_error. Since *that*
        # is called from inside the asyncio event loop, we can't actulaly just
        # schedule it on the loop - without starting another thread with its
        # own loop, which seems not worth the trouble. Instead, we create another
        # environment, exactly like this one, but sync
        del jinja_options["enable_async"]
        self.jinja_env_sync = Environment(loader=loader, **jinja_options)
