"""Pruebas de route-map, local-preference y comunidades, pieza por pieza (poli/poli.clab.yml)."""
import os
import unittest

from netlab import HERE, Lab, wait_until

LAB = Lab("poli", "w06-poli", "redes-w06-poli")
ALL = {f"100.64.{n}.0/24" for n in (130, 131, 132, 133)}
TO_P, TO_Q, TO_C = "100.64.131.129", "100.64.130.130", "100.64.130.134"


def conf(name):
    return os.path.join(HERE, "poli", name)


def setUpModule():
    unittest.addModuleCleanup(LAB.destroy)
    LAB.deploy()
    if not LAB.wait_for_table(["m", "p", "q", "c"], ALL):
        raise RuntimeError("la topología no quedó lista")


class A_NoPolicy(unittest.TestCase):
    def test_shorter_path_wins_without_policy(self):
        self.assertEqual(LAB.best_path("m", "100.64.131.0/24"), [65310])

    def test_m_passes_its_peer_prefix_to_its_provider(self):
        self.assertIn("100.64.132.0/24", LAB.advertised("m", TO_P))

    def test_frr_requires_policy_on_ebgp_unless_told_otherwise(self):
        on = ("configure terminal", "router bgp 65330", "bgp ebgp-requires-policy")
        off = ("configure terminal", "router bgp 65330", "no bgp ebgp-requires-policy")
        self.addCleanup(lambda: wait_until(lambda: LAB.best_path("c", "100.64.131.0/24"), seconds=30))
        self.addCleanup(LAB.vtysh, "c", "clear bgp *")
        self.addCleanup(LAB.vtysh, "c", *off)
        LAB.vtysh("c", *on)
        LAB.vtysh("c", "clear bgp *")
        self.assertTrue(wait_until(lambda: "(Policy)" in LAB.vtysh("c", "show bgp summary"),
                                   seconds=30), LAB.vtysh("c", "show bgp summary"))


class B_LocalPref(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        LAB.load_conf("m", conf("step1_localpref.conf"))

    def test_local_pref_beats_a_shorter_as_path(self):
        self.assertTrue(wait_until(lambda: LAB.best_path("m", "100.64.131.0/24") == [65320, 65310],
                                   seconds=20))
        self.assertTrue(wait_until(lambda: "best (Local Pref)" in
                                   LAB.vtysh("m", "show bgp ipv4 100.64.131.0/24"), seconds=20))

    def test_local_pref_does_not_leave_the_as(self):
        self.assertTrue(wait_until(lambda: LAB.best_path("m", "100.64.131.0/24") == [65320, 65310],
                                   seconds=20))
        self.assertNotIn("localpref", LAB.vtysh("c", "show bgp ipv4 100.64.131.0/24"))


class C_Community(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        LAB.load_conf("m", conf("step2_community.conf"))

    def test_community_tags_the_route_inside_m(self):
        self.assertTrue(wait_until(lambda: "Community: 65300:30" in
                                   LAB.vtysh("m", "show bgp ipv4 100.64.133.0/24"), seconds=20))

    def test_community_travels_to_other_ases(self):
        self.assertTrue(wait_until(lambda: "65300:30" in
                                   LAB.vtysh("p", "show bgp ipv4 100.64.133.0/24"), seconds=20))

    def test_other_routes_are_not_tagged(self):
        self.assertNotIn("Community", LAB.vtysh("m", "show bgp ipv4 100.64.132.0/24"))


class D_Export(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        wait_until(lambda: "65300:30" in LAB.vtysh("m", "show bgp ipv4 100.64.133.0/24"), seconds=20)
        LAB.load_conf("m", conf("step3_export.conf"))
        wait_until(lambda: LAB.advertised("m", TO_P) == {"100.64.130.0/24", "100.64.133.0/24"},
                   seconds=30)

    def test_only_own_and_tagged_routes_go_to_p(self):
        self.assertEqual(LAB.advertised("m", TO_P), {"100.64.130.0/24", "100.64.133.0/24"})

    def test_p_lost_the_path_through_m_to_q(self):
        ok = wait_until(lambda: "65300" not in LAB.vtysh("p", "show bgp ipv4 100.64.132.0/24"),
                        seconds=20)
        self.assertTrue(ok, LAB.vtysh("p", "show bgp ipv4 100.64.132.0/24"))

    def test_other_neighbours_are_not_affected(self):
        self.assertIn("100.64.131.0/24", LAB.advertised("m", TO_C))
        self.assertIn("100.64.133.0/24", LAB.advertised("m", TO_Q))


class E_ImplicitDeny(unittest.TestCase):
    """Lo que un route-map no deja pasar de forma explícita, se bota."""

    def apply_to_q(self, name):
        LAB.vtysh("m", "configure terminal", "router bgp 65300", "address-family ipv4 unicast",
                  f"neighbor {TO_Q} route-map {name} out")
        self.addCleanup(LAB.vtysh, "m", "configure terminal", "router bgp 65300",
                        "address-family ipv4 unicast", f"no neighbor {TO_Q} route-map {name} out")

    def test_route_map_that_matches_nothing_announces_nothing(self):
        LAB.vtysh("m", "configure terminal", "bgp community-list standard NOBODY permit 65300:99",
                  "route-map ONLY_NOBODY permit 10", "match community NOBODY")
        self.apply_to_q("ONLY_NOBODY")
        self.assertTrue(wait_until(lambda: LAB.advertised("m", TO_Q) == set(), seconds=30))

    def test_undefined_route_map_announces_nothing(self):
        wait_until(lambda: LAB.advertised("m", TO_Q) != set(), seconds=30)
        self.apply_to_q("TYPO_IN_THE_NAME")
        self.assertTrue(wait_until(lambda: LAB.advertised("m", TO_Q) == set(), seconds=30))


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