import logging
import time
import urllib
import socket
import gevent

from gevent.pywsgi import WSGIServer
from lib.gevent_ws import WebSocketHandler

from .UiRequest import UiRequest
from Site import SiteManager
from Config import config
from Debug import Debug
import importlib

from util import helper

# Skip websocket handler if not necessary
class UiWSGIHandler(WebSocketHandler):

    def __init__(self, *args, **kwargs):
        self.server = args[2]
        super(UiWSGIHandler, self).__init__(*args, **kwargs)
        self.args = args
        self.kwargs = kwargs

    def handleError(self, err):
        if config.debug:  # Allow websocket errors to appear on /Debug
            import main
            main.DebugHook.handleError()
        else:
            ui_request = UiRequest(self.server, self.environ, self.start_response, is_data_request=False)
            block_gen = ui_request.error500("UiWSGIHandler error: %s" % Debug.formatExceptionMessage(err))
            for block in block_gen:
                self.write(block)

    def run_application(self):
        err_name = "UiWSGIHandler websocket" if "HTTP_UPGRADE" in self.environ else "UiWSGIHandler"
        try:
            super(UiWSGIHandler, self).run_application()
        except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError) as err:
            logging.warning("%s connection error: %s" % (err_name, err))
        except Exception as err:
            logging.warning("%s error: %s" % (err_name, Debug.formatException(err)))
            self.handleError(err)

    def handle(self):
        # Save socket to be able to close them properly on exit
        self.server.sockets[self.client_address] = self.socket
        super(UiWSGIHandler, self).handle()
        del self.server.sockets[self.client_address]


class UiServer:
    def __init__(self):
        self.ip = config.ui_ip
        self.port = config.ui_port
        self.site_port = config.ui_site_port
        self.running = False
        if self.ip == "*":
            self.ip = "0.0.0.0"  # Bind all
        if config.ui_host:
            self.allowed_hosts = set(config.ui_host)
        elif config.ui_ip == "127.0.0.1":
            # IP Addresses are inherently allowed as they are immune to DNS
            # rebinding attacks.
            self.allowed_hosts = set(["zero", "localhost:%s" % config.ui_port])
            # "URI producers and normalizers should omit the port component and
            # its ':' delimiter if port is empty or if its value would be the
            # same as that of the scheme's default."
            # Source: https://tools.ietf.org/html/rfc3986#section-3.2.3
            # As a result, we need to support portless hosts if port 80 is in
            # use.
            if config.ui_port == 80:
                self.allowed_hosts.update(["localhost"])
        else:
            self.allowed_hosts = set([])
        self.allowed_ws_origins = set()
        self.allow_trans_proxy = config.ui_trans_proxy

        self.wrapper_nonces = []
        self.add_nonces = []
        self.websockets = []
        self.site_manager = SiteManager.site_manager
        self.sites = SiteManager.site_manager.list()
        self.log = logging.getLogger(__name__)
        config.error_logger.onNewRecord = self.handleErrorLogRecord

    def handleErrorLogRecord(self, record):
        self.updateWebsocket(log_event=record.levelname)

    # After WebUI started
    def afterStarted(self):
        from util import Platform
        Platform.setMaxfilesopened(config.max_files_opened)

    # Handle WSGI request
    def handleRequest(self, env, start_response):
        path = bytes(env["PATH_INFO"], "raw-unicode-escape").decode("utf8")
        ui_request = UiRequest(self, env, start_response, is_data_request=False)
        if config.debug:  # Let the exception catched by werkezung
            return ui_request.route(path)
        else:  # Catch and display the error
            try:
                return ui_request.route(path)
            except Exception as err:
                logging.debug("UiRequest error: %s" % Debug.formatException(err))
                return ui_request.error500("Err: %s" % Debug.formatException(err))

    # Reload the UiRequest class to prevent restarts in debug mode
    def reload(self):
        global UiRequest
        import imp
        import sys
        importlib.reload(sys.modules["User.UserManager"])
        importlib.reload(sys.modules["Ui.UiWebsocket"])
        UiRequest = imp.load_source("UiRequest", "src/Ui/UiRequest.py").UiRequest
        # UiRequest.reload()

    # Bind and run the server
    def start(self):
        self.running = True
        handler = self.handleRequest

        if config.debug:
            # Auto reload UiRequest on change
            from Debug import DebugReloader
            DebugReloader.watcher.addCallback(self.reload)

            # Werkzeug Debugger
            try:
                from werkzeug.debug import DebuggedApplication
                handler = DebuggedApplication(self.handleRequest, evalex=True)
            except Exception as err:
                self.log.info("%s: For debugging please download Werkzeug (http://werkzeug.pocoo.org/)" % err)
                from Debug import DebugReloader
        self.log.write = lambda msg: self.log.debug(msg.strip())  # For Wsgi access.log
        self.log.info("--------------------------------------")
        if ":" in config.ui_ip:
            self.log.info("Web interface: http://[%s]:%s/" % (config.ui_ip, config.ui_port))
        else:
            self.log.info("Web interface: http://%s:%s/" % (config.ui_ip, config.ui_port))
        self.log.info("--------------------------------------")

        helper.openBrowser(config.open_browser)

        self.server = WSGIServer((self.ip, self.port), handler, handler_class=UiWSGIHandler, log=self.log)
        self.server.sockets = {}
        self.afterStarted()
        try:
            self.server.serve_forever()
        except Exception as err:
            self.log.error("Web interface bind error, must be running already, exiting.... %s" % err)
            import main
            main.file_server.stop()
        self.log.debug("Stopped.")

    def handleSiteRequest(self, env, start_response):
        path = bytes(env["PATH_INFO"], "raw-unicode-escape").decode("utf8")
        ui_request = UiRequest(self, env, start_response, is_data_request=True)
        try:
            return ui_request.route(path)
        except Exception as err:
            logging.debug(f"UiRequest @ site error: {Debug.formatException(err)}")
            return ui_request.error500('Error while trying to serve site data')

    def startSiteServer(self):
        self.site_server = WSGIServer((self.ip, self.site_port), self.handleSiteRequest, log=self.log)
        self.site_server.serve_forever()

    def stop(self):
        self.log.debug("Stopping...")
        # Close WS sockets
        if "clients" in dir(self.server):
            for client in list(self.server.clients.values()):
                client.ws.close()
        # Close http sockets
        sock_closed = 0
        for sock in list(self.server.sockets.values()):
            try:
                sock.send(b"bye")
                sock.shutdown(socket.SHUT_RDWR)
                # sock._sock.close()
                # sock.close()
                sock_closed += 1
            except Exception as err:
                self.log.debug("Http connection close error: %s" % err)
        self.log.debug("Socket closed: %s" % sock_closed)
        time.sleep(0.1)
        if config.debug:
            from Debug import DebugReloader
            DebugReloader.watcher.stop()

        self.server.socket.close()
        self.server.stop()
        self.running = False
        time.sleep(1)

    def updateWebsocket(self, **kwargs):
        if kwargs:
            param = {"event": list(kwargs.items())[0]}
        else:
            param = None

        for ws in self.websockets:
            ws.event("serverChanged", param)
