"""Ayudas de las pruebas de la semana 4: levantar topologías y correr comandos."""
import os
import subprocess
import time
import unittest

HERE = os.path.dirname(os.path.abspath(__file__))
# La solución no se publica. Sin ella, las pruebas usan lo que ustedes escriben en lab/.
ANSWERS = os.path.join(HERE, "solution")
if not os.path.isdir(ANSWERS):
    ANSWERS = os.path.join(HERE, "lab")


def answer_path(name):
    """Devuelve la ruta de un archivo del laboratorio, o salta si sigue con el TODO."""
    path = os.path.join(ANSWERS, name)
    with open(path, encoding="utf-8") as file:
        if "TODO" in file.read():
            raise unittest.SkipTest(f"falta completar lab/{name}")
    return path


def run(args, timeout=240, stdin=None):
    return subprocess.run(args, capture_output=True, text=True, timeout=timeout, stdin=stdin)


class Lab:
    """Una topología de Containerlab que se levanta y se destruye desde las pruebas."""

    def __init__(self, folder, name, network):
        self.folder = os.path.join(HERE, folder)
        self.topology = f"{folder}.clab.yml"
        self.prefix = f"clab-{name}-"
        self.network = network

    def deploy(self):
        done = subprocess.run(
            ["containerlab", "deploy", "-t", self.topology, "--reconfigure"],
            cwd=self.folder, capture_output=True, text=True, timeout=300)
        if done.returncode != 0:
            raise RuntimeError(done.stdout + done.stderr)

    def destroy(self):
        subprocess.run(["containerlab", "destroy", "-t", self.topology, "--cleanup"],
                       cwd=self.folder, capture_output=True, text=True, timeout=300)
        subprocess.run(["docker", "network", "rm", self.network], capture_output=True, text=True)

    def sh(self, node, *command, timeout=60):
        """Corre un comando en un nodo y devuelve el resultado completo."""
        return run(["docker", "exec", self.prefix + node, *command], timeout=timeout)

    def out(self, node, *command, timeout=60):
        """Corre un comando en un nodo y devuelve stdout y stderr juntos."""
        done = self.sh(node, *command, timeout=timeout)
        return done.stdout + done.stderr

    def daemon(self, node, command):
        """Deja un comando corriendo en segundo plano dentro de un nodo."""
        run(["docker", "exec", "-d", self.prefix + node, "sh", "-c", command])

    def load_nft(self, node, path):
        with open(path, encoding="utf-8") as file:
            done = run(["docker", "exec", "-i", self.prefix + node, "nft", "-f", "-"], stdin=file)
        if done.returncode != 0:
            raise RuntimeError(done.stderr)

    def python(self, node, code):
        """Corre un programa corto de Python en un nodo."""
        done = subprocess.run(["docker", "exec", "-i", self.prefix + node, "python3", "-"],
                              input=code, capture_output=True, text=True, timeout=60)
        return done.stdout + done.stderr

    def capture(self, node, interface, expression, count, seconds=5, flags=("-n", "-e", "-v")):
        """Empieza una captura; el resultado se pide con capture.result()."""
        return Capture(self.prefix + node, interface, expression, count, seconds, flags)


class Capture:
    def __init__(self, container, interface, expression, count, seconds, flags):
        self.process = subprocess.Popen(
            ["docker", "exec", container, "timeout", str(seconds), "tcpdump", "-l", *flags,
             "-i", interface, "-c", str(count), expression],
            stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
        time.sleep(0.7)  # darle tiempo a tcpdump de abrir la interfaz

    def result(self):
        output, _ = self.process.communicate(timeout=30)
        return output


def wait_until(predicate, seconds=30, step=0.5):
    """Espera a que una condición se cumpla; devuelve si se cumplió."""
    deadline = time.monotonic() + seconds
    while time.monotonic() < deadline:
        if predicate():
            return True
        time.sleep(step)
    return predicate()
