"""Native adapter for serving CherryPy via its builtin server."""

import logging
import sys
import io

import cheroot.server

import cherrypy
from cherrypy._cperror import format_exc, bare_error
from cherrypy.lib import httputil
from ._cpcompat import tonative


class NativeGateway(cheroot.server.Gateway):
    """Native gateway implementation allowing to bypass WSGI."""

    recursive = False

    def respond(self):
        """Obtain response from CherryPy machinery and then send it."""
        req = self.req
        try:
            # Obtain a Request object from CherryPy
            local = req.server.bind_addr  # FIXME: handle UNIX sockets
            local = tonative(local[0]), local[1]
            local = httputil.Host(local[0], local[1], '')
            remote = tonative(req.conn.remote_addr), req.conn.remote_port
            remote = httputil.Host(remote[0], remote[1], '')

            scheme = tonative(req.scheme)
            sn = cherrypy.tree.script_name(tonative(req.uri or '/'))
            if sn is None:
                self.send_response('404 Not Found', [], [''])
            else:
                app = cherrypy.tree.apps[sn]
                method = tonative(req.method)
                path = tonative(req.path)
                qs = tonative(req.qs or '')
                headers = (
                    (tonative(h), tonative(v))
                    for h, v in req.inheaders.items()
                )
                rfile = req.rfile
                prev = None

                try:
                    redirections = []
                    while True:
                        request, response = app.get_serving(
                            local, remote, scheme, 'HTTP/1.1')
                        request.multithread = True
                        request.multiprocess = False
                        request.app = app
                        request.prev = prev

                        # Run the CherryPy Request object and obtain the
                        # response
                        try:
                            request.run(
                                method, path, qs,
                                tonative(req.request_protocol),
                                headers, rfile,
                            )
                            break
                        except cherrypy.InternalRedirect:
                            ir = sys.exc_info()[1]
                            app.release_serving()
                            prev = request

                            if not self.recursive:
                                if ir.path in redirections:
                                    raise RuntimeError(
                                        'InternalRedirector visited the same '
                                        'URL twice: %r' % ir.path)
                                else:
                                    # Add the *previous* path_info + qs to
                                    # redirections.
                                    if qs:
                                        qs = '?' + qs
                                    redirections.append(sn + path + qs)

                            # Munge environment and try again.
                            method = 'GET'
                            path = ir.path
                            qs = ir.query_string
                            rfile = io.BytesIO()

                    self.send_response(
                        response.output_status, response.header_list,
                        response.body)
                finally:
                    app.release_serving()
        except Exception:
            tb = format_exc()
            # print tb
            cherrypy.log(tb, 'NATIVE_ADAPTER', severity=logging.ERROR)
            s, h, b = bare_error()
            self.send_response(s, h, b)

    def send_response(self, status, headers, body):
        """Send response to HTTP request."""
        req = self.req

        # Set response status
        req.status = status or b'500 Server Error'

        # Set response headers
        for header, value in headers:
            req.outheaders.append((header, value))
        if (req.ready and not req.sent_headers):
            req.sent_headers = True
            req.send_headers()

        # Set response body
        for seg in body:
            req.write(seg)


class CPHTTPServer(cheroot.server.HTTPServer):
    """Wrapper for cheroot.server.HTTPServer.

    cheroot has been designed to not reference CherryPy in any way,
    so that it can be used in other frameworks and applications.
    Therefore, we wrap it here, so we can apply some attributes
    from config -> cherrypy.server -> HTTPServer.
    """

    def __init__(self, server_adapter=cherrypy.server):
        """Initialize CPHTTPServer."""
        self.server_adapter = server_adapter

        server_name = (self.server_adapter.socket_host or
                       self.server_adapter.socket_file or
                       None)

        cheroot.server.HTTPServer.__init__(
            self, server_adapter.bind_addr, NativeGateway,
            minthreads=server_adapter.thread_pool,
            maxthreads=server_adapter.thread_pool_max,
            server_name=server_name)

        self.max_request_header_size = (
            self.server_adapter.max_request_header_size or 0)
        self.max_request_body_size = (
            self.server_adapter.max_request_body_size or 0)
        self.request_queue_size = self.server_adapter.socket_queue_size
        self.timeout = self.server_adapter.socket_timeout
        self.shutdown_timeout = self.server_adapter.shutdown_timeout
        self.protocol = self.server_adapter.protocol_version
        self.nodelay = self.server_adapter.nodelay

        ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
        if self.server_adapter.ssl_context:
            adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module)
            self.ssl_adapter = adapter_class(
                self.server_adapter.ssl_certificate,
                self.server_adapter.ssl_private_key,
                self.server_adapter.ssl_certificate_chain,
                self.server_adapter.ssl_ciphers)
            self.ssl_adapter.context = self.server_adapter.ssl_context
        elif self.server_adapter.ssl_certificate:
            adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module)
            self.ssl_adapter = adapter_class(
                self.server_adapter.ssl_certificate,
                self.server_adapter.ssl_private_key,
                self.server_adapter.ssl_certificate_chain,
                self.server_adapter.ssl_ciphers)
