#!/usr/bin/env python3
"""El tablero del hackathon: quién alcanza a quién, y qué sesiones BGP no están arriba.

    python3 tablero.py [archivo_de_equipos]        por omisión, ensayo/equipos.txt

El archivo tiene una línea por equipo: nombre, contenedor y dirección. Cada celda es un
ping desde la dirección de la fila hacia la de la columna: que dé "ok" exige que el camino
exista en los dos sentidos.
"""
import json
import os
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor

HERE = os.path.dirname(os.path.abspath(__file__))
DEFAULT_TEAMS = os.path.join(HERE, "ensayo", "equipos.txt")


def read_teams(path=DEFAULT_TEAMS):
    teams = []
    with open(path, encoding="utf-8") as file:
        for line in file:
            line = line.split("#")[0].strip()
            if line:
                name, container, address = line.split()
                teams.append((name, container, address))
    return teams


def can_reach(source, target):
    command = ["docker", "exec", source[1], "ping", "-c", "2", "-W", "1", "-I", source[2], target[2]]
    return subprocess.run(command, capture_output=True, text=True, timeout=30).returncode == 0


def reachability(teams):
    """Devuelve {(origen, destino): True o False} para cada par de equipos distintos."""
    pairs = [(a, b) for a in teams for b in teams if a is not b]
    with ThreadPoolExecutor(max_workers=32) as pool:
        results = list(pool.map(lambda pair: can_reach(*pair), pairs))
    return {(a[0], b[0]): good for (a, b), good in zip(pairs, results)}


def sessions_down(teams):
    """Devuelve las sesiones BGP que no están en Established: (equipo, vecino, estado)."""
    down = []
    for name, container, _ in teams:
        done = subprocess.run(["docker", "exec", container, "vtysh", "-c", "show bgp summary json"],
                              capture_output=True, text=True, timeout=30)
        try:
            summary = json.loads(done.stdout)
        except json.JSONDecodeError:
            down.append((name, "?", "sin respuesta de vtysh"))
            continue
        peers = summary.get("ipv4Unicast", {}).get("peers", {})
        for address, peer in peers.items():
            if peer.get("state") != "Established":
                down.append((name, address, peer.get("state", "?")))
    return down


def render(teams, matrix):
    names = [team[0] for team in teams]
    width = max(len(name) for name in names) + 2
    lines = [" " * width + "".join(f"{name:>{width}}" for name in names)]
    for row in names:
        cells = ["-" if row == column else ("ok" if matrix[row, column] else "X")
                 for column in names]
        lines.append(f"{row:<{width}}" + "".join(f"{cell:>{width}}" for cell in cells))
    good = sum(matrix.values())
    lines.append(f"{good} de {len(matrix)} pares se alcanzan")
    return "\n".join(lines)


def main():
    path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_TEAMS
    teams = read_teams(path)
    print(render(teams, reachability(teams)))
    down = sessions_down(teams)
    print("sesiones BGP que no están arriba:" if down else "todas las sesiones BGP están arriba")
    for name, address, state in down:
        print(f"  {name}: vecino {address}, {state}")


if __name__ == "__main__":
    main()
