"""SSL Probe Functionality"""
import asyncio
from collections import namedtuple
import ssl

from cryptography import x509
from cryptography.x509.oid import NameOID

# Setup a SSL context that will connect to anything
_ssl = ssl.create_default_context()
_ssl.check_hostname = False
_ssl.verify_mode = ssl.CERT_NONE
try:
    try:
        _ssl.minimum_version = ssl.TLSVersion.TLSv1  # STM-6823
    except AttributeError:
        pass  # Python 3.6
    _ssl.set_ciphers('DEFAULT@SECLEVEL=1')  # STM-4531
except ssl.SSLError:
    pass  # some platforms don't allow this, ok

SslProbeResult = namedtuple('SslProbeResult', ('error', 'org', 'cn'))


async def ssl_probe(host, port):
    """Try to connect to an SSL endpoint, and return certificate data."""
    try:
        loop = asyncio.get_running_loop()
    except AttributeError:
        # This is running python 3.6 or earlier; we don't support it.
        return SslProbeResult('UnsupportedPythonVersion', None, None)
    try:
        transport, proto = await asyncio.wait_for(loop.create_connection(
            asyncio.Protocol, host, port, ssl=_ssl, ssl_handshake_timeout=3.0),
                                                  timeout=5.0)
        sslobj = transport.get_extra_info('ssl_object')
        cert = sslobj.getpeercert(binary_form=True)
        cn = o = None
        if cert:
            cert = x509.load_der_x509_certificate(cert)
            cn = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
            cn = cn[0].value if cn else None
            o = cert.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)
            o = o[0].value if o else None
        transport.close()
        return SslProbeResult(None, o, cn)
    except ValueError:
        # some bad field in the cert, e.g. STM-9048
        return SslProbeResult('BadCert', None, None)
    except ConnectionAbortedError:
        # likely SSL handshake error
        return SslProbeResult('BadHandshake', None, None)
    except (asyncio.TimeoutError, ConnectionError, OSError) as e:
        return SslProbeResult('NoConnection', None, None)
