"""Pruebas del ensayo del hackathon: las fallas se ven como dicen los apuntes y, con los
arreglos de lab/, el tablero se llena. Las de los arreglos se saltan si hay TODO."""
import re
import unittest

import ensayo
import tablero
from netlab import Lab, answer_path, wait_until

LAB = Lab("ensayo", "w09-ensayo", "redes-w09-ensayo")
TEAMS = tablero.read_teams()
NAMES = [team[0] for team in TEAMS]


def setUpModule():
    unittest.addModuleCleanup(LAB.destroy)
    LAB.deploy()
    # Esperamos a que estén arriba las sesiones que sí pueden subir.
    if not wait_until(sessions_settled, seconds=90, step=2):
        raise RuntimeError("las sesiones no subieron como se esperaba:\n" + LAB.vtysh("t1", "show bgp summary"))


def sessions_settled():
    down = tablero.sessions_down(TEAMS)
    return [item[:2] for item in down] == [("e1", "10.209.2.1")]


def pairs_that_fail():
    matrix = tablero.reachability(TEAMS)
    return {pair for pair, good in matrix.items() if not good}


def everything_except(*good):
    pairs = {(a, b) for a in NAMES for b in NAMES if a != b}
    return pairs - set(good) - {(b, a) for a, b in good}


class A_TheFaults(unittest.TestCase):
    """Corre antes de cargar los arreglos: el orden alfabético de las clases importa."""

    def test_board_shows_only_the_peering_alive(self):
        expected = everything_except(("e3", "e4"))
        self.assertTrue(wait_until(lambda: pairs_that_fail() == expected, seconds=40, step=3),
                        pairs_that_fail())

    def test_e1_has_the_wrong_as_for_its_provider(self):
        self.assertIn("64091", LAB.vtysh("e1", "show bgp summary"))
        state = LAB.vtysh("e1", "show bgp neighbors 10.209.2.1")
        self.assertNotIn("BGP state = Established", state)

    def test_e2_network_statement_matches_nothing(self):
        route = LAB.vtysh("e2", "show bgp ipv4 unicast 100.64.21.0/24")
        self.assertIn("import-check", route)
        self.assertIn("Not advertised", route)
        self.assertIn("Network not in table", LAB.vtysh("t1", "show bgp ipv4 unicast 100.64.12.0/24"))

    def test_t2_filter_rejects_the_announcement_of_e4(self):
        self.assertIn("Network not in table", LAB.vtysh("t2", "show bgp ipv4 unicast 100.64.14.0/24"))
        self.assertIn("100.64.14.0/24",
                      LAB.vtysh("e4", "show bgp ipv4 unicast neighbors 10.209.7.1 advertised-routes"))

    def test_e3_says_nothing_to_t2(self):
        table = LAB.vtysh("e3", "show bgp ipv4 unicast neighbors 10.209.6.1 advertised-routes")
        self.assertNotIn("100.64.13.0/24", table)
        self.assertIn("100.64.13.0/24",
                      LAB.vtysh("e3", "show bgp ipv4 unicast neighbors 10.209.4.1 advertised-routes"))


class B_TheFixes(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        for router in ensayo.FIXED_ROUTERS:
            answer_path(f"{router}.conf")
        ensayo.load_fixes()

    def setUp(self):
        self.assertTrue(wait_until(lambda: not pairs_that_fail(), seconds=90, step=3),
                        f"siguen sin alcanzarse: {sorted(pairs_that_fail())}")

    def cut_and_expect(self, a, b, expected):
        ensayo.set_link(a, b, "down")
        self.addCleanup(ensayo.set_link, a, b, "up")
        self.assertTrue(wait_until(lambda: pairs_that_fail() == expected, seconds=60, step=3),
                        f"sin {a}-{b}, fallan {sorted(pairs_that_fail())}")

    def test_01_every_team_reaches_every_team(self):
        self.assertEqual(tablero.sessions_down(TEAMS), [])

    def test_02_two_providers_survive_losing_one(self):
        self.cut_and_expect("t1", "e3", set())

    def test_03_a_peer_does_not_replace_a_provider(self):
        self.cut_and_expect("t2", "e4", {("e1", "e4"), ("e4", "e1"), ("e2", "e4"), ("e4", "e2")})

    def test_04_cutting_the_peering_of_the_providers_splits_the_single_homed(self):
        self.cut_and_expect("t1", "t2", {("e1", "e4"), ("e4", "e1")})

    def test_05_one_provider_means_isolated(self):
        expected = {(a, b) for a in NAMES for b in NAMES if a != b and "e1" in (a, b)}
        self.cut_and_expect("t1", "e1", expected)

    def test_06_render_says_how_many(self):
        text = tablero.render(TEAMS, tablero.reachability(TEAMS))
        self.assertTrue(re.search(r"12 de 12 pares", text), text)


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