import sys
import logging
import os
import os.path
import random
import ssl
import tempfile
import argparse

if sys.version_info.major == 3:
    import http.server
    import socketserver
    http_server = http.server
elif sys.version_info.major < 3:
    import SocketServer
    import BaseHTTPServer
    import SimpleHTTPServer
    http_server = SimpleHTTPServer

try:
    import OpenSSL.crypto
    OPENSSL = True
except ImportError:
    OPENSSL = False

from .. import DeenPlugin
from deen.exceptions import MissingDependencyException


class DeenPluginListener(DeenPlugin):
    name = 'listener'
    display_name = 'Listener'
    aliases = ['listen',
               'http',
               'https',
               'tcp',
               'tls',
               'ssl']
    cmd_name = 'listener'
    cmd_help = 'Listen for HTTP/TCP/SSL/TLS connections.'
    cmd_desc = ('A generic listener plugin that supports '
                'opening HTTP or raw TCP ports on a \ngiven TCP port. '
                'It also supports HTTPS and raw SSL/TLS sockets '
                'with either \nrandomly generated or user-supplied '
                'certificates. For HTTP ports the current \ndirectory '
                'will be served.\n\n'
                'The different modes can be called via the shortcuts '
                'http, https, tcp, ssl, \ntls without providing '
                'additional options as well.')
    cmd_only = True

    def __init__(self):
        super(DeenPluginListener, self).__init__()
        self.listen_host = b'127.0.0.1'
        self.listen_port = 8000
        self.listen_socket = ()
        self.serving_directory = os.getcwd()
        self.ca_common_name = 'Deen CA'
        self.ca_cert_pem = None
        self.ca_key_pem = None
        self.server_cert_pem = None
        self.server_key_pem = None

    def prerequisites(self):
        try:
            import OpenSSL.crypto
        except ImportError:
            self.log_missing_depdendencies('pyOpenSSL')
            return False
        else:
            return True

    @staticmethod
    def add_argparser(argparser, *args, **kwargs):
        # Python 2 argparse does not support aliases
        if sys.version_info.major < 3 or \
            (sys.version_info.major == 3 and
                sys.version_info.minor < 2):
            parser = argparser.add_parser(DeenPluginListener.cmd_name,
                                          formatter_class=argparse.RawDescriptionHelpFormatter,
                                          description=DeenPluginListener.cmd_desc,
                                          help=DeenPluginListener.cmd_help)
        else:
            parser = argparser.add_parser(DeenPluginListener.cmd_name,
                                          formatter_class=argparse.RawDescriptionHelpFormatter,
                                          description=DeenPluginListener.cmd_desc,
                                          help=DeenPluginListener.cmd_help,
                                          aliases=DeenPluginListener.aliases)
        parser.add_argument('port', nargs='?', type=int, default=8000,
                            help='listening port (default: 8000)')
        parser.add_argument('-d', '--directory', default=os.getcwd(), type=str,
                            metavar='directory', dest='pluginvar_directory')
        parser.add_argument('-b', '--b', help='listening host (default: 0.0.0.0)',
                            type=str, default='0.0.0.0', metavar='host', dest='pluginvar_host')
        parser.add_argument('-t', '--tcp', dest='pluginvar_tcp', default=False,
                            help='open a raw tcp listener',
                            action='store_true')
        parser.add_argument('-s', '--ssl', dest='pluginvar_ssl', default=False,
                            help='use ssl/tls on the listening port',
                            action='store_true')
        parser.add_argument('--server-cert', default=None, type=str,
                            metavar='file', dest='pluginvar_server_cert')
        parser.add_argument('--server-key', default=None, type=str,
                            metavar='file', dest='pluginvar_server_key')

    def process_cli(self, args):
        if not OPENSSL:
            self.error = MissingDependencyException('pyOpenSSL is not available')
            return
        # TODO: check user input
        self.listen_socket = (args.pluginvar_host,
                              args.port)
        self.serving_directory = args.pluginvar_directory
        self.server_cert_file = args.pluginvar_server_cert
        self.server_key_file = args.pluginvar_server_key
        if args.plugin_cmd == 'ssl' or args.plugin_cmd == 'https' \
                or args.pluginvar_ssl:
            if self.server_cert_file and self.server_key_file:
                self._read_cert_and_key_files()
            else:
                self._generate_pki()
        if args.plugin_cmd == 'ssl':
            if args.pluginvar_tcp:
                self.ssl_listen()
            else:
                self.https_listen()
        elif args.plugin_cmd == 'tcp' or args.pluginvar_tcp:
            if args.pluginvar_ssl:
                self.ssl_listen()
            else:
                self.tcp_listen()
        else:
            if args.pluginvar_ssl or args.plugin_cmd =='https':
                self.https_listen()
            else:
                self.http_listen()

    def _read_cert_and_key_files(self):
        """Read the certificate and private key from the
        file paths provided via CLI arguments."""
        if not os.path.isfile(self.server_cert_file) or \
                not os.path.isfile(self.server_key_file):
            return
        with open(self.server_cert_file, 'rb') as f:
            cert = f.read()
            try:
                OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
            except (OpenSSL.crypto.Error, TypeError) as e:
                self.error = e
                self.log.error(self.error)
                self.log.debug(self.error, exc_info=True)
                return