import re
import os.path
import json
import datetime
import logging

import twisted.internet.endpoints
import twisted.internet.threads
import twisted.web.wsgi
import twisted.web.static
import twisted.web.resource
import twisted.web.server
import twisted.logger
import twisted.python.logfile

import mini_buildd.config

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 FileResource(twisted.web.static.File):
    """Twisted static resource enhanced with switchable index and regex matching support."""

    def __init__(self, *args, with_index=False, uri_regex=".*", **kwargs):
        super().__init__(*args, **kwargs)
        self.mbd_with_index = with_index
        self.mbd_uri_regex = re.compile(uri_regex)

    def directoryListing(self):  # noqa (pep8 N802)
        if not self.mbd_with_index:
            return self.forbidden
        return super().directoryListing()

    def getChild(self, path, request):  # noqa (pep8 N802)
        """Custom getChild implementation."""
        if not self.mbd_uri_regex.match(request.uri.decode("utf-8")):
            return self.forbidden
        child = super().getChild(path, request)
        child.mbd_with_index = self.mbd_with_index
        child.mbd_uri_regex = self.mbd_uri_regex
        return child


class Events(twisted.web.resource.Resource):
    @classmethod
    def _render(cls, request):
        try:
            LOG.debug(f"{request.channel}: Waiting for event...")
            import mini_buildd.daemon  # pylint: disable=redefined-outer-name
            since = datetime.datetime.fromisoformat(request.args[b"since"][0].decode(mini_buildd.config.CHAR_ENCODING)) if b"since" in request.args else None
            queue = mini_buildd.daemon.get().events.attach(request.channel, since=mini_buildd.misc.datetime_check_aware(since))
            event = queue.get()
            LOG.debug(f"{request.channel}: Got event: {event}")
            request.write(json.dumps(event.to_json()).encode(mini_buildd.config.CHAR_ENCODING))
            request.finish()
            queue.task_done()
        except Exception as e:
            request.setResponseCode(509, f"{e}".encode(mini_buildd.config.CHAR_ENCODING))
            request.finish()

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


class HttpD():
    DOC_MISSING_HTML = """\
<html><body>
<h1>Online Manual Not Available (<code>mini-buildd-doc</code> not installed?)</h1>
Package <b><code>mini-buildd-doc</code></b> is not installed on this site (might be intentional to save space).
<p><a href="/">[Back]</a></p>
</body></html>
"""

    def _add_static_route(self, route, directory, with_index=False, uri_regex=".*", with_doc_missing_error=False):
        static = FileResource(with_index=with_index, uri_regex=uri_regex, path=directory)

        if with_doc_missing_error:
            static.childNotFound = twisted.web.resource.NoResource(self.DOC_MISSING_HTML)

        for k, v in self._mime_types.items():
            static.contentTypes[f".{k}"] = v
        self.resource.putChild(bytes(route, encoding=mini_buildd.config.CHAR_ENCODING), static)

    def __init__(self, wsgi_app):
        mime_text_plain = f"text/plain; charset={mini_buildd.config.CHAR_ENCODING}"
        self._mime_types = {"log": mime_text_plain,
                            "buildlog": mime_text_plain,
                            "changes": mime_text_plain,
                            "dsc": mime_text_plain}

        # Note: This import selects and initializes the twisted's default reactor.
        # Note: damonize() functionality (as currently used in mini-buildd) will break this if run before this import.
        from twisted.internet import reactor
        self.reactor = reactor

        # Bend twisted (not access.log) logging to ours
        twisted.logger.globalLogPublisher.addObserver(twisted.logger.STDLibLogObserver(name=__name__))

        # HTTP setup
        self.resource = RootResource(twisted.web.wsgi.WSGIResource(self.reactor, self.reactor.getThreadPool(), wsgi_app))  # pylint: disable=no-member
        self.site = Site(self.resource, logPath=mini_buildd.config.ACCESS_LOG_FILE)

        for ep in mini_buildd.config.HTTP_ENDPOINTS:
            twisted.internet.endpoints.serverFromString(self.reactor, ep.description).listen(self.site)

        # Custom static routes
        self._add_static_route(mini_buildd.config.HTTP_PATH_STATIC,
                               f"{mini_buildd.config.PY_PACKAGE_PATH}/mini_buildd/static")
        self._add_static_route(mini_buildd.config.HTTP_PATH_DOC,
                               mini_buildd.config.MANUAL_DIR,
                               with_doc_missing_error=True)
        self._add_static_route(mini_buildd.config.HTTP_PATH_REPOSITORIES,
                               mini_buildd.config.REPOSITORIES_DIR,
                               with_index=True,
                               uri_regex=rf"^/{mini_buildd.config.HTTP_PATH_REPOSITORIES}/.+/(pool|dists)/.*")
        self._add_static_route(mini_buildd.config.HTTP_PATH_LOG,
                               mini_buildd.config.LOG_DIR,
                               with_index=True,
                               uri_regex=r"^/log/.+/.*")

        # Events route
        self.resource.putChild(bytes(mini_buildd.config.HTTP_PATH_EVENTS, encoding=mini_buildd.config.CHAR_ENCODING), Events())

    def run(self):
        self.reactor.run(installSignalHandlers=0)  # pylint: disable=no-member
