#!/usr/bin/env python3
"""Un controlador de juguete: traduce una política match-action a reglas en el leaf l1.

    python3 sdn_ctl.py install <politica>   instala la política en l1
    python3 sdn_ctl.py show                 muestra las reglas y sus contadores
    python3 sdn_ctl.py remove               quita todo: el tráfico vuelve a seguir a BGP

Cada línea de la política dice: prioridad | match | acción. El match es una expresión de nft y
la acción es drop, via s1 o via s2. No es OpenFlow y corre una sola vez; lo que muestra es la
separación: aquí se decide, y el leaf solo aplica lo que le instalan. Con "via", la regla le gana
a BGP: si ese spine muere, el leaf sigue mandando allí hasta que el controlador cambie la regla.
"""
import sys

import netlab
import scenarios
from scenarios import LAB

ACTIONS = {"drop": "drop", "via s1": "meta mark set 1 accept", "via s2": "meta mark set 2 accept"}


def parse_policy(path):
    """Lee la política y devuelve [(prioridad, match, acción)], de mayor a menor prioridad."""
    rules = []
    with open(path, encoding="utf-8") as file:
        for number, line in enumerate(file, 1):
            line = line.split("#")[0].strip()
            if not line:
                continue
            parts = [part.strip() for part in line.split("|")]
            if len(parts) != 3 or parts[2] not in ACTIONS or not parts[0].isdigit():
                raise ValueError(f"{path}:{number}: se esperaba prioridad | match | acción")
            rules.append((int(parts[0]), parts[1], parts[2]))
    return sorted(rules, key=lambda rule: -rule[0])


def compile_rules(rules):
    """Convierte las reglas en una tabla de nftables, en una sola transacción."""
    body = " ".join(f"{match} counter {ACTIONS[action]};" for _, match, action in rules)
    return ("table ip w14steer { chain pre { type filter hook prerouting priority mangle; "
            f"{body} }}; }}")


def install(path):
    rules = parse_policy(path)
    scenarios.steer_tables()
    scenarios.nft_install(compile_rules(rules))
    print(f"instaladas {len(rules)} reglas en l1")


def show():
    print(LAB.out("l1", "nft", "list", "table", "ip", "w14steer"))


def main():
    netlab.ECHO = True
    if len(sys.argv) == 3 and sys.argv[1] == "install":
        install(sys.argv[2])
    elif len(sys.argv) == 2 and sys.argv[1] == "show":
        show()
    elif len(sys.argv) == 2 and sys.argv[1] == "remove":
        scenarios.unsteer()
    else:
        print(__doc__)
        return 2
    return 0


if __name__ == "__main__":
    sys.exit(main())
