from __future__ import print_function, division, absolute_import
from gevent import monkey
monkey.patch_all()
import os

import socket
import gevent.testing as greentest
# Be careful not to have TestTCP as a bare attribute in this module,
# even aliased, to avoid running duplicate tests
from gevent.tests import test__socket
import ssl


def ssl_listener(private_key, certificate):
    raw_listener = socket.socket()
    greentest.bind_and_listen(raw_listener)
    # pylint:disable=deprecated-method
    sock = wrap_socket(raw_listener, keyfile=private_key, certfile=certificate,
                       server_side=True)
    return sock, raw_listener

def wrap_socket(sock, *, keyfile=None, certfile=None, server_side=False):
    context = ssl.SSLContext(
        protocol=ssl.PROTOCOL_TLS
    )
    context.verify_mode = ssl.CERT_NONE
    context.check_hostname = False
    context.load_default_certs()
    if keyfile is not None or certfile is not None:
        context.load_cert_chain(certfile=certfile, keyfile=keyfile)
    return context.wrap_socket(sock, server_side=server_side)

class TestSSL(test__socket.TestTCP):

    # To generate:
    # openssl req -x509 -newkey rsa:4096 -keyout test_server.key -out test_server.crt -days 36500 -nodes -subj '/CN=localhost'
    certfile = os.path.join(os.path.dirname(__file__), 'test_server.crt')
    privfile = os.path.join(os.path.dirname(__file__), 'test_server.key')
    # Python 2.x has socket.sslerror (which  is an alias for
    # ssl.SSLError); That's gone in Py3 though. In Python 2, most timeouts are raised
    # as SSLError, but Python 3 raises the normal socket.timeout instead. So this has
    # the effect of making TIMEOUT_ERROR be SSLError on Py2 and socket.timeout on Py3
    # See https://bugs.python.org/issue10272.
    # PyPy3 7.2 has a bug, though: it shares much of the SSL implementation with Python 2,
    # and it unconditionally does `socket.sslerror = SSLError` when ssl is imported.
    # So we can't rely on getattr/hasattr tests, we must be explicit.
    TIMEOUT_ERROR = socket.timeout # pylint:disable=no-member

    def _setup_listener(self):
        listener, raw_listener = ssl_listener(self.privfile, self.certfile)
        self._close_on_teardown(raw_listener)
        return listener

    def create_connection(self, *args, **kwargs): # pylint:disable=signature-differs
        return self._close_on_teardown(
            # pylint:disable=deprecated-method
            wrap_socket(super(TestSSL, self).create_connection(*args, **kwargs)))

    # The SSL library can take a long time to buffer the large amount of data we're trying
    # to send, so we can't compare to the timeout values
    _test_sendall_timeout_check_time = False

    # The SSL layer has extra buffering, so test_sendall needs
    # to send a very large amount to make it timeout
    _test_sendall_data = data_sent = b'hello' * 100000000

    test_sendall_array = greentest.skipOnMacOnCI("Sometimes misses data")(
        greentest.skipOnManylinux("Sometimes misses data")(
            test__socket.TestTCP.test_sendall_array
        )
    )

    test_sendall_str = greentest.skipOnMacOnCI("Sometimes misses data")(
        greentest.skipOnManylinux("Sometimes misses data")(
            test__socket.TestTCP.test_sendall_str
        )
    )

    @greentest.skipOnWindows("Not clear why we're skipping")
    def test_ssl_sendall_timeout0(self):
        # Issue #317: SSL_WRITE_PENDING in some corner cases

        server_sock = []
        acceptor = test__socket.Thread(target=lambda: server_sock.append(
            # pylint:disable=no-member
            self.listener.accept()))
        client = self.create_connection()
        client.setblocking(False)
        try:
            # Python 3 raises ssl.SSLWantWriteError; Python 2 simply *hangs*
            # on non-blocking sockets because it's a simple loop around
            # send(). Python 2.6 doesn't have SSLWantWriteError
            expected = getattr(ssl, 'SSLWantWriteError', ssl.SSLError)
            with self.assertRaises(expected):
                client.sendall(self._test_sendall_data)
        finally:
            acceptor.join()
            client.close()
            server_sock[0][0].close()

    # def test_fullduplex(self):
    #     try:
    #         super(TestSSL, self).test_fullduplex()
    #     except LoopExit:
    #         if greentest.LIBUV and greentest.WIN:
    #             # XXX: Unable to duplicate locally
    #             raise greentest.SkipTest("libuv on Windows sometimes raises LoopExit")
    #         raise

    @greentest.ignores_leakcheck
    @greentest.skipOnPy310("No longer raises SSLError")
    def test_empty_send(self):
        # Issue 719
        # Sending empty bytes with the 'send' method raises
        # ssl.SSLEOFError in the stdlib. PyPy 4.0 and CPython 2.6
        # both just raise the superclass, ssl.SSLError.

        # Ignored during leakchecks because the third or fourth iteration of the
        # test hangs on CPython 2/posix for some reason, likely due to
        # the use of _close_on_teardown keeping something alive longer than intended.
        # cf test__makefile_ref
        with self.assertRaises(ssl.SSLError):
            super(TestSSL, self).test_empty_send()

    @greentest.ignores_leakcheck
    def test_sendall_nonblocking(self):
        # Override; doesn't work with SSL sockets.
        pass

    @greentest.ignores_leakcheck
    def test_connect_with_type_flags_ignored(self):
        # Override; doesn't work with SSL sockets.
        pass



if __name__ == '__main__':
    greentest.main()
