import datetime
import json
import logging
import os.path

import twisted.internet.endpoints
import twisted.internet.threads
import twisted.python.log
import twisted.python.logfile
import twisted.python.threadpool
import twisted.web.resource
import twisted.web.server
import twisted.web.static
import twisted.web.wsgi
from twisted.internet import reactor

from mini_buildd import config, daemon, events, net, threads, util

LOG = logging.getLogger(__name__)


class Site(twisted.web.server.Site):
    def _openLogFile(self, path):  # noqa (pep8 N802)
        return twisted.python.logfile.LogFile(os.path.basename(path), directory=os.path.dirname(path), rotateLength=5000000, maxRotatedFiles=9)


class RootResource(twisted.web.resource.Resource):
    """Twisted root resource needed to mix static and wsgi resources"""

    def __init__(self, wsgi_resource):
        super().__init__()
        self._wsgi_resource = wsgi_resource

    def getChild(self, path, request):  # noqa (pep8 N802)
        request.prepath.pop()
        request.postpath.insert(0, path)
        return self._wsgi_resource


class Events(twisted.web.resource.Resource):
    @classmethod
    def _render(cls, request):
        LOG.debug("%s: Waiting for event...", request.channel)
        after = datetime.datetime.fromisoformat(request.args[b"after"][0].decode(config.CHAR_ENCODING)) if b"after" in request.args else None
        queue = daemon.get().events.attach(request.channel, after=util.Datetime.check_aware(after))
        event = events.get(queue)
        LOG.debug("%s: Got event: %s", request.channel, event)
        request.write(json.dumps(event.to_json()).encode(config.CHAR_ENCODING))
        request.finish()

    @classmethod
    def _on_error(cls, failure, request):
        http_exception = util.e2http(failure.value)
        request.setResponseCode(http_exception.status, f"{http_exception}".encode(config.CHAR_ENCODING))
        if getattr(request, "_disconnected", False):
            request.notifyFinish()
        else:
            request.finish()

    def render_GET(self, request):  # noqa (pep8 N802)
        request.setHeader("Content-Type", f"application/json; charset={config.CHAR_ENCODING}")
        twisted.internet.threads.deferToThread(self._render, request).addErrback(self._on_error, request)
        return twisted.web.server.NOT_DONE_YET


class HttpD(threads.Thread):
    def _add_route(self, uri, resource):
        """Add route from (possibly nested) path from config -- making sure already existing parent routes are reused"""
        LOG.debug("Adding route: %s -> %s", uri, resource)

        uri_split = uri.twisted().split("/")
        res = self.resource
        for u in [uri_split[0:p] for p in range(1, len(uri_split))]:
            path = "/".join(u)
            sub = self.route_hierarchy.get(path)
            if sub is None:
                sub = twisted.web.resource.Resource()
                res.putChild(bytes(u[-1], encoding=config.CHAR_ENCODING), sub)
                self.route_hierarchy[path] = sub
            res = sub
        res.putChild(bytes(uri_split[-1], encoding=config.CHAR_ENCODING), resource)

    def __init__(self, wsgi_app, minthreads=0, maxthreads=10):
        # Use python's native logging system
        twisted.python.log.PythonLoggingObserver().start()

        self.endpoints = [net.ServerEndpoint(ep, protocol="http") for ep in config.HTTP_ENDPOINTS]
        super().__init__()
        self.route_hierarchy = {}

        # HTTP setup
        self.thread_pool = twisted.python.threadpool.ThreadPool(minthreads, maxthreads, name=self.__class__.__name__)
        self.resource = RootResource(twisted.web.wsgi.WSGIResource(reactor, self.thread_pool, wsgi_app))
        self.site = Site(self.resource, logPath=config.ROUTES["log"].path.join(config.ACCESS_LOG_FILE))

        # Static routes
        for route in config.ROUTES.values():
            for uri in (uri for key, uri in route.uris.items() if key.startswith("static")):
                self._add_route(uri, twisted.web.static.File(route.path.full, defaultType=f"text/plain; charset={config.CHAR_ENCODING}"))

        # Events route
        self._add_route(config.ROUTES["events"].uris["attach"], Events())

        # Start sockets
        ep_errors = []

        def on_ep_error(f):
            ep_errors.append(f.value)

        for ep in self.endpoints:
            twisted.internet.endpoints.serverFromString(reactor, ep.description).listen(self.site).addErrback(on_ep_error)

        for e in ep_errors:
            raise util.HTTPUnavailable(str(e))

    def __str__(self):
        return f"{super().__str__()} ({','.join([ep.geturl() for ep in self.endpoints])})"

    def shutdown(self):
        self.thread_pool.stop()
        reactor.stop()

    def mbd_run(self):
        self.thread_pool.start()
        reactor.run(installSignalHandlers=0)
