from pathlib import Path

import otree
from otree import settings
from starlette.responses import HTMLResponse

from .errors import TemplateLoadError


class FileLoader:
    def __init__(self, *dirs):
        self.dirs = dirs
        self.cache = {}

    def load(self, filename: str, template_type=None):
        if filename in self.cache:
            return self.cache[filename]

        template, path = self.load_from_disk(filename, template_type=template_type)
        self.cache[filename] = template
        return template

    def search_template(self, template_id) -> Path:
        for dir in self.dirs:
            path = Path(dir, template_id)
            if path.exists():
                return path
        msg = f"Loader cannot locate the template file '{template_id}'."
        raise TemplateLoadError(msg)

    def load_from_disk(self, template_id, template_type) -> tuple:
        from .template import Template  # todo: resolve circular import

        abspath = self.search_template(template_id)
        try:
            template_string = abspath.read_text('utf-8')
        except OSError as err:
            msg = f"FileLoader cannot load the template file '{abspath}'."
            raise TemplateLoadError(msg) from err
        template = Template(template_string, template_id, template_type=template_type)
        return template, abspath


class FileReloader(FileLoader):
    def load(self, filename: str, template_type=None):
        if filename in self.cache:
            cached_mtime, cached_path, cached_template = self.cache[filename]
            if cached_path.exists() and cached_path.stat().st_mtime == cached_mtime:
                return cached_template
        template, path = self.load_from_disk(filename, template_type=template_type)
        mtime = path.stat().st_mtime
        self.cache[filename] = (mtime, path, template)
        return template


def get_ibis_loader():
    loader_class = FileReloader if settings.DEBUG else FileLoader

    dirs = [
        Path('.'),  # for noself
        Path('_templates'),
        Path(otree.__file__).parent.joinpath('templates'),
    ] + [Path(app_name, 'templates') for app_name in settings.OTREE_APPS]
    return loader_class(*dirs)


ibis_loader = get_ibis_loader()


def get_template_name_if_exists(template_names) -> str:
    '''return the path of the first template that exists'''
    for fname in template_names:
        try:
            ibis_loader.load(fname)
        except TemplateLoadError:
            pass
        else:
            return fname
    raise TemplateLoadError(str(template_names))


def render(template_name, context, template_type=None, **extra_context):
    return HTMLResponse(
        ibis_loader.load(template_name, template_type=template_type).render(
            context, **extra_context, strict_mode=True
        )
    )
    # i used to modify the traceback to report the original error,
    # but actually i think we shouldn't.
    # The main case I had in mind was if the user calls a method like
    # player.foo(), but it's simpler if they just don't call any complex methods
    # to begin with, and just pass variables to the template.
    # that way we don't go against thre grain
