"""Pruebas del laboratorio: se saltan hasta que completen lab/abr.py y lab/mkchain.sh."""
import os
import socket
import subprocess
import tempfile
import time
import unittest

import dash_sim
from netlab import answer_path


def free_port():
    with socket.socket() as sock:
        sock.bind(("127.0.0.1", 0))
        return sock.getsockname()[1]


class Abr(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.choose = staticmethod(dash_sim.load_choose(answer_path("abr.py")))

    def test_never_freezes_when_the_network_falls(self):
        rows, frozen = dash_sim.simulate(self.choose)
        self.assertEqual(frozen, 0.0, "se congeló la imagen")
        self.assertGreater(dash_sim.score(rows)[0], 1_200_000, "se queda muy abajo")

    def test_jumps_less_than_last_sample(self):
        mine, _ = dash_sim.simulate(self.choose, network=dash_sim.OSCILLATING)
        theirs, _ = dash_sim.simulate(dash_sim.last_sample, network=dash_sim.OSCILLATING)
        self.assertLess(dash_sim.score(mine)[1], dash_sim.score(theirs)[1])

    def test_returns_a_valid_index_with_no_samples(self):
        index = self.choose(dash_sim.BITRATES, [], 0.0)
        self.assertIn(index, range(len(dash_sim.BITRATES)))


class Chain(unittest.TestCase):
    NAME = "cadena.redes.test"

    @classmethod
    def setUpClass(cls):
        script = answer_path("mkchain.sh")
        cls.folder = tempfile.TemporaryDirectory()
        cls.dir = cls.folder.name
        done = subprocess.run(["sh", script, cls.dir, cls.NAME], capture_output=True, text=True, timeout=30)
        if done.returncode != 0:
            cls.folder.cleanup()
            raise AssertionError("mkchain.sh falló: " + done.stderr)

    @classmethod
    def tearDownClass(cls):
        cls.folder.cleanup()

    def path(self, name):
        return os.path.join(self.dir, name)

    def extension(self, cert, name):
        return subprocess.run(["openssl", "x509", "-in", self.path(cert), "-noout", "-ext", name],
                              capture_output=True, text=True).stdout

    def serve_and_fetch(self, *flags):
        port = free_port()
        server = subprocess.Popen(
            ["openssl", "s_server", "-accept", str(port), "-www", "-cert", self.path("leaf.crt"),
             "-key", self.path("leaf.key"), *flags],
            stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        try:
            time.sleep(0.5)
            return subprocess.run(["curl", "-sS", "--max-time", "5", "--cacert", self.path("root.crt"),
                                   "--resolve", f"{self.NAME}:{port}:127.0.0.1", "-o", "/dev/null",
                                   f"https://{self.NAME}:{port}/"], capture_output=True, text=True)
        finally:
            server.kill()
            server.wait()

    def test_verify_accepts_the_chain(self):
        done = subprocess.run(["openssl", "verify", "-CAfile", self.path("root.crt"), "-untrusted",
                               self.path("intermediate.crt"), self.path("leaf.crt")],
                              capture_output=True, text=True)
        self.assertEqual(done.returncode, 0, done.stderr)

    def test_verify_fails_without_the_intermediate(self):
        done = subprocess.run(["openssl", "verify", "-CAfile", self.path("root.crt"), self.path("leaf.crt")],
                              capture_output=True, text=True)
        self.assertNotEqual(done.returncode, 0)

    def test_constraints(self):
        self.assertIn("CA:TRUE", self.extension("root.crt", "basicConstraints"))
        middle = self.extension("intermediate.crt", "basicConstraints")
        self.assertIn("CA:TRUE", middle)
        self.assertIn("pathlen:0", middle)
        self.assertIn("CA:FALSE", self.extension("leaf.crt", "basicConstraints"))
        self.assertIn(f"DNS:{self.NAME}", self.extension("leaf.crt", "subjectAltName"))

    def test_curl_accepts_when_the_server_sends_the_intermediate(self):
        done = self.serve_and_fetch("-cert_chain", self.path("intermediate.crt"))
        self.assertEqual(done.returncode, 0, done.stderr)

    def test_curl_rejects_a_server_that_forgets_the_intermediate(self):
        done = self.serve_and_fetch()
        self.assertEqual(done.returncode, 60, done.stderr)


if __name__ == "__main__":
    unittest.main()
