"""Pasos 1 a 3 del laboratorio, en la topología de un switch y cuatro hosts."""
import re
import time
import unittest

import labutil

H1, H2, H3, H4 = (f"02:00:00:00:00:0{i}" for i in range(1, 5))


class Lan(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.addClassCleanup(labutil.destroy, "lan")
        labutil.deploy("lan")
        cls.lab = labutil.Lab("lan")
        time.sleep(6)  # pasan los mensajes de los primeros segundos

    def setUp(self):
        for host in ("h1", "h2", "h3", "h4"):
            self.lab.sh(host, "ip neigh flush dev eth1")
        self.lab.sh("sw1", "ip link set br0 type bridge fdb_flush")

    def fdb(self):
        text = self.lab.out("sw1", "bridge fdb show br br0 | grep -v permanent")
        return {m.group(1): m.group(2) for m in re.finditer(r"(\S+) dev (eth\d)", text)}

    def test_1_arp_request_is_broadcast_and_reply_is_unicast(self):
        lines = self.lab.capture("h1", "eth1", lambda: self.lab.ping("h1", "10.203.0.2"), "arp")
        self.assertRegex(lines[0], rf"{H1} > ff:ff:ff:ff:ff:ff.*Request who-has 10.203.0.2")
        self.assertRegex(lines[1], rf"{H2} > {H1}.*Reply 10.203.0.2 is-at {H2}")
        self.assertIn(H2, self.lab.out("h1", "ip neigh show dev eth1"))

    def test_1_target_learns_the_sender_from_the_request(self):
        self.lab.ping("h1", "10.203.0.2")
        self.assertIn(H1, self.lab.out("h2", "ip neigh show dev eth1"))

    def test_1_other_hosts_get_the_request_but_not_the_reply(self):
        lines = self.lab.capture("h3", "eth1", lambda: self.lab.ping("h1", "10.203.0.2"), "arp")
        self.assertEqual(len(lines), 1, lines)
        self.assertIn("Request", lines[0])
        self.assertNotIn(H2, self.lab.out("h3", "ip neigh show dev eth1"))

    def test_2_switch_learns_from_source_addresses(self):
        self.assertEqual(self.fdb(), {})
        self.lab.ping("h1", "10.203.0.2")
        self.assertEqual(self.fdb(), {H1: "eth1", H2: "eth2"})

    def test_2_unknown_unicast_is_flooded_once(self):
        self.lab.ping("h1", "10.203.0.2", 1)
        self.lab.sh("sw1", "ip link set br0 type bridge fdb_flush")
        # h1 conserva su entrada ARP: envía directo a la MAC de h2, que el switch ya olvidó.
        lines = self.lab.capture("h3", "eth1", lambda: self.lab.ping("h1", "10.203.0.2", 3), "icmp")
        self.assertEqual(len(lines), 1, lines)
        self.assertIn("echo request", lines[0])

    def test_2_without_learning_the_bridge_behaves_like_a_hub(self):
        self.lab.ping("h1", "10.203.0.2", 1)
        self.lab.sh("sw1", "ip link set br0 type bridge ageing_time 0", check=True)
        try:
            lines = self.lab.capture("h3", "eth1", lambda: self.lab.ping("h1", "10.203.0.2", 3),
                                     "icmp")
        finally:
            self.lab.sh("sw1", "ip link set br0 type bridge ageing_time 30000", check=True)
        self.assertEqual(len(lines), 6, lines)

    def test_arp_probe_finds_the_neighbor(self):
        done = self.lab.sh("h1", "arp_probe eth1 10.203.0.4")
        self.assertEqual(done.returncode, 0, done.stdout)
        self.assertIn("la MAC del vecino es " + H4, done.stdout)
        self.assertIn("ff ff ff ff ff ff 02 00 00 00 00 01 08 06", done.stdout)

    def test_arp_probe_reports_no_answer(self):
        done = self.lab.sh("h1", "arp_probe eth1 10.203.0.99")
        self.assertEqual(done.returncode, 1)
        self.assertIn("sin respuesta", done.stdout)

    def test_zz_step_3_vlans_split_the_network(self):
        # Se corre al final (orden alfabético): deja el switch con VLANs.
        self.lab.apply_script("sw1", "vlan/lan_sw1.sh")
        self.assertTrue(self.lab.ping("h1", "10.203.0.2", 2))
        self.assertTrue(self.lab.ping("h3", "10.203.0.4", 2))
        lines = self.lab.capture("h3", "eth1", lambda: self.lab.ping("h1", "10.203.0.3", 2), "arp")
        self.assertEqual(lines, [], "un broadcast de la VLAN 110 no debe llegar a la 120")
        self.assertFalse(self.lab.ping("h1", "10.203.0.4", 1))
        self.assertRegex(self.lab.out("sw1", "bridge fdb show br br0 | grep -v permanent"),
                         rf"{H1} dev eth1 vlan 110")


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