import argparse
from io import BytesIO
import logging
import os
import posixpath
import socket
from tempfile import TemporaryFile
import time
from urllib import quote

from waitress.server import create_server
import ZConfig
import Zope2
from Zope2.Startup.run import make_wsgi_app

from Products.ERP5Type.patches.WSGIPublisher import publish_module


# this class licensed under the MIT license (stolen from pyramid_translogger)
class TransLogger(object):

    format = ('%(REMOTE_ADDR)s - %(REMOTE_USER)s [%(time)s] '
              '"%(REQUEST_METHOD)s %(REQUEST_URI)s %(HTTP_VERSION)s" '
              '%(status)s %(bytes)s "%(HTTP_REFERER)s" "%(HTTP_USER_AGENT)s"')

    def __init__(self, application, logger):
        self.application = application
        self.logger = logger

    def __call__(self, environ, start_response):
        start = time.localtime()
        req_uri = quote(environ.get('SCRIPT_NAME', '')
                               + environ.get('PATH_INFO', ''))
        if environ.get('QUERY_STRING'):
            req_uri += '?'+environ['QUERY_STRING']
        method = environ['REQUEST_METHOD']
        def replacement_start_response(status, headers, exc_info=None):
            # @@: Ideally we would count the bytes going by if no
            # content-length header was provided; but that does add
            # some overhead, so at least for now we'll be lazy.
            bytes = None
            for name, value in headers:
                if name.lower() == 'content-length':
                    bytes = value
            self.write_log(environ, method, req_uri, start, status, bytes)
            return start_response(status, headers)
        return self.application(environ, replacement_start_response)

    def write_log(self, environ, method, req_uri, start, status, bytes):
        if bytes is None:
            bytes = '-'
        if time.daylight:
                offset = time.altzone / 60 / 60 * -100
        else:
                offset = time.timezone / 60 / 60 * -100
        if offset >= 0:
                offset = "+%0.4d" % (offset)
        elif offset < 0:
                offset = "%0.4d" % (offset)
        d = {
            'REMOTE_ADDR': environ.get('REMOTE_ADDR') or '-',
            'REMOTE_USER': environ.get('REMOTE_USER') or '-',
            'REQUEST_METHOD': method,
            'REQUEST_URI': req_uri,
            'HTTP_VERSION': environ.get('SERVER_PROTOCOL'),
            'time': time.strftime('%d/%b/%Y:%H:%M:%S ', start) + offset,
            'status': status.split(None, 1)[0],
            'bytes': bytes,
            'HTTP_REFERER': environ.get('HTTP_REFERER', '-'),
            'HTTP_USER_AGENT': environ.get('HTTP_USER_AGENT', '-'),
            }
        message = self.format % d
        self.logger.warn(message)


def app_wrapper(large_file_threshold, use_webdav):
    try:
        from product.DeadlockDebugger.dumper import dump_threads, dump_url
    except Exception:
        dump_url = '\0'
    def app(environ, start_response):
        path_info = environ['PATH_INFO']
        if dump_url.startswith(path_info):
            query_string = environ['QUERY_STRING']
            if dump_url == (path_info + '?' + query_string if query_string
                            else path_info):
                start_response('200 OK', (('Content-type', 'text/plain'),))
                return [dump_threads()]

        original_wsgi_input = environ['wsgi.input']
        if not hasattr(original_wsgi_input, 'seek'):
            # Convert environ['wsgi.input'] to a file-like object.
            cl = environ.get('CONTENT_LENGTH')
            cl = int(cl) if cl else 0
            if cl > large_file_threshold:
                new_wsgi_input = environ['wsgi.input'] = TemporaryFile('w+b')
            else:
                new_wsgi_input = environ['wsgi.input'] = BytesIO()

            rest = cl
            chunksize = 1<<20
            try:
                while chunksize < rest:
                    new_wsgi_input.write(original_wsgi_input.read(chunksize))
                    rest -= chunksize
                if rest:
                    new_wsgi_input.write(original_wsgi_input.read(rest))
            except (socket.error, IOError):
                msg = b'Not enough data in request or socket error'
                start_response('400 Bad Request', [
                    ('Content-Type', 'text/plain'),
                    ('Content-Length', str(len(msg))),
                    ]
                )
                return [msg]
            new_wsgi_input.seek(0)

        if use_webdav:
            # Munge the request to ensure that we call manage_FTPGet.

            # Set a flag to indicate this request came through the WebDAV source
            # port server.
            environ['WEBDAV_SOURCE_PORT'] = 1

            if environ['REQUEST_METHOD'] == 'GET':
                if os.sep != '/':
                    path_info =  path_info.replace(os.sep, '/')
                path_info = posixpath.join(path_info, 'manage_DAVget')
                path_info = posixpath.normpath(path_info)
                environ['PATH_INFO'] = path_info

        return publish_module(environ, start_response)
    return app


def runwsgi():
    global server

    parser = argparse.ArgumentParser()
    parser.add_argument('-w', '--webdav', action='store_true')
    parser.add_argument('address', help='<ip>:<port>')
    parser.add_argument('zope_conf', help='path to zope.conf')
    args = parser.parse_args()

    startup = os.path.dirname(Zope2.Startup.__file__)
    schema = ZConfig.loadSchema(os.path.join(startup, 'zopeschema.xml'))
    conf, _ = ZConfig.loadConfig(schema, args.zope_conf)

    make_wsgi_app({}, zope_conf=args.zope_conf)

    server = create_server(
        TransLogger(app_wrapper(conf.large_file_threshold, args.webdav),
                    logger=logging.getLogger("access")),
        listen=args.address,
        threads=conf.zserver_threads,
        trusted_proxy='*',
        trusted_proxy_headers=('x-forwarded-for',),
        clear_untrusted_proxy_headers=True,
    )
    server.run()
