#!/usr/bin/env python
import orjson
import falcon.asgi
from falcon import media
from helpers import load_template, FortuneTuple, generate_ids, sanitize
from operator import attrgetter
from random import randint
from tortoise import Tortoise
from tortoise_models import World, Fortune
from tortoise.transactions import in_transaction, atomic
from tortoise.exceptions import OperationalError


# Middleware
class TortoiseInit:
    async def process_startup(self, scope, event):
        await Tortoise.init(
            db_url="postgres://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world",
            modules={"models": ["tortoise_models"]}
        )
        await Tortoise.generate_schemas(safe=True)

    async def process_shutdown(self, scopre, event):
        await Tortoise.close_connections()


tortoise_init = TortoiseInit()


# resource endpoints
class JSONResource(object):
    async def on_get(self, request, response):
        response.media = {'message': "Hello, world!"}


class SingleQuery(object):
    # Note: There's much improvement when we decorate
    # the query, even just for retreiving data
    @atomic()
    async def on_get(self, request, response):
        resp = await World.get(id=randint(1, 10000))
        response.media = resp.to_dict()


class MultipleQueries(object):
    # Note: Not much different between using atomic or
    # in_transaction decorator here.
    @atomic()
    async def on_get(self, request, response, num):
        num = sanitize(num)
        worlds = []
        for ids in generate_ids(num):
            data = await World.get(id=ids)
            worlds.append(data.to_dict())
        response.media = worlds


class UpdateQueries(object):
    async def on_get(self, request, response, num):
        try:
            async with in_transaction():
                num = sanitize(num)
                items = await World.filter(id__in=generate_ids(num))
                worlds = []
                for ids in items:
                    ids.randomNumber = randint(1, 10000)
                    await ids.save()
                    worlds.append({'id': ids.id, 'randomNumber': ids.randomNumber})
                response.media = worlds
        except OperationalError:
            pass


class Fortunes(object):
    _template = load_template()

    async def on_get(self, request, response):
        fortunes = [FortuneTuple(id=f.id, message=f.message) for f in await Fortune.all()]
        fortunes.append(FortuneTuple(id=0, message="Additional fortune added at request time."))
        fortunes.sort(key=attrgetter("message"))
        content = self._template.render(fortunes=fortunes)
        response.content_type = falcon.MEDIA_HTML
        response.text = content


class PlaintextResource(object):
    async def on_get(self, request, response):
        response.content_type = falcon.MEDIA_TEXT
        response.text = 'Hello, world!'


asgi = falcon.asgi.App(middleware=[tortoise_init])

# Change JSON handler to orjson
JSONHandler = media.JSONHandler(dumps=orjson.dumps, loads=orjson.loads)
extra_handlers = {
    "application/json": JSONHandler,
    "application/json; charset=UTF-8": JSONHandler
}
asgi.req_options.media_handlers.update(extra_handlers)
asgi.resp_options.media_handlers.update(extra_handlers)

# register resources
asgi.add_route("/json", JSONResource())
asgi.add_route("/db", SingleQuery())
asgi.add_route("/queries/{num}", MultipleQueries())
asgi.add_route("/updates/{num}", UpdateQueries())
asgi.add_route("/fortunes", Fortunes())
asgi.add_route("/plaintext", PlaintextResource())
