#!/usr/bin/python3
"""
network related utilities
"""
import contextlib
import http.server
import socket
import ssl
import threading

try:
    from http.server import ThreadingHTTPServer
except ImportError:
    # This fallback is only needed on py3.6. Py3.7+ has ThreadingHTTPServer.
    # We just import ThreadingHTTPServer here so that the import of "net.py"
    # on py36 works, the helpers are not usable because the "directory" arg
    # for SimpleHTTPRequestHandler is also not supported.
    class ThreadingHTTPServer:  # type: ignore
        def __init__(self, *args, **kwargs):  # pylint: disable=unused-argument
            # pylint: disable=import-outside-toplevel
            import pytest  # type: ignore
            pytest.skip("python too old to suport ThreadingHTTPServer")


from .atomic import AtomicCounter


def _get_free_port():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("localhost", 0))
    return s.getsockname()[1]


class SilentHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    def log_message(self, *args, **kwargs):
        pass

    def do_GET(self):
        # silence errors when the other side "hangs up" unexpectedly
        # (our tests will do that when downloading in parallel)
        try:
            super().do_GET()
        except (ConnectionResetError, BrokenPipeError):
            pass


class DirHTTPServer(ThreadingHTTPServer):
    def __init__(self, *args, directory=None, simulate_failures=0, **kwargs):
        super().__init__(*args, **kwargs)
        self.directory = directory
        self.simulate_failures = AtomicCounter(simulate_failures)
        self.reqs = AtomicCounter()

    def finish_request(self, request, client_address):
        self.reqs.inc()
        if self.simulate_failures.count > 0:
            self.simulate_failures.dec()
            SilentHTTPRequestHandler(
                request, client_address, self, directory="does-not-exists")
            return
        SilentHTTPRequestHandler(
            request, client_address, self, directory=self.directory)


def _httpd(rootdir, simulate_failures, ctx=None):
    port = _get_free_port()
    httpd = DirHTTPServer(
        ("localhost", port),
        http.server.SimpleHTTPRequestHandler,
        directory=rootdir,
        simulate_failures=simulate_failures,
    )
    if ctx:
        httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
    threading.Thread(target=httpd.serve_forever).start()
    return httpd


@contextlib.contextmanager
def http_serve_directory(rootdir, simulate_failures=0):
    httpd = _httpd(rootdir, simulate_failures)
    try:
        yield httpd
    finally:
        httpd.shutdown()


@contextlib.contextmanager
def https_serve_directory(rootdir, certfile, keyfile, simulate_failures=0):
    ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ctx.load_cert_chain(certfile=certfile, keyfile=keyfile)
    httpd = _httpd(rootdir, simulate_failures, ctx)
    try:
        yield httpd
    finally:
        httpd.shutdown()


@contextlib.contextmanager
def https_serve_directory_mtls(rootdir, ca_cert, server_cert, server_key, simulate_failures=0):
    ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=ca_cert)
    ctx.load_cert_chain(certfile=server_cert, keyfile=server_key)
    ctx.verify_mode = ssl.CERT_REQUIRED
    httpd = _httpd(rootdir, simulate_failures, ctx)
    try:
        yield httpd
    finally:
        httpd.shutdown()
