"""Pruebas de los programas en C: cidr, lpm e ipv4_header."""
import os
import subprocess
import tempfile
import unittest

from netlab import HERE

# Encabezado capturado en r1 (eth1): TTL 64, antes del forwarding.
CAPTURED = "\t0x0000:  4500 0054 73b5 4000 4001 ad50 0ac8 010a\n" \
           "\t0x0010:  0ac8 030a 0800 5973 0099 0000 e30f bae3\n"


def tool(name, *args, stdin=None):
    return subprocess.run([os.path.join(HERE, name), *args], capture_output=True, text=True,
                          input=stdin, timeout=10)


def internet_checksum(header):
    """Suma de complemento a uno de palabras de 16 bits (solo para verificar la captura)."""
    total = sum(int.from_bytes(header[i:i + 2], "big") for i in range(0, len(header), 2))
    while total >> 16:
        total = (total & 0xffff) + (total >> 16)
    return ~total & 0xffff


class CidrTest(unittest.TestCase):
    def test_info_of_22(self):
        out = tool("cidr", "10.20.0.0/22").stdout
        self.assertIn("255.255.252.0", out)
        self.assertIn("10.20.3.255", out)
        self.assertIn("(1022)", out)

    def test_split_guide_example(self):
        out = tool("cidr", "10.20.0.0/22", "4").stdout
        for sub in ("10.20.0.0/24", "10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"):
            self.assertIn(sub, out)

    def test_host_bits_are_reported(self):
        out = tool("cidr", "10.1.2.3/24").stdout
        self.assertIn("bits de host", out)
        self.assertIn("10.1.2.0/24", out)

    def test_edges(self):
        self.assertIn("255.255.255.254", tool("cidr", "10.0.0.0/31").stdout)
        self.assertIn("(2)", tool("cidr", "10.0.0.0/31").stdout)
        self.assertIn("(1)", tool("cidr", "10.0.0.1/32").stdout)
        self.assertIn("0.0.0.0", tool("cidr", "0.0.0.0/0").stdout)

    def test_invalid_input(self):
        self.assertEqual(tool("cidr", "10.0.0.0/33").returncode, 2)
        self.assertEqual(tool("cidr", "300.0.0.0/8").returncode, 2)
        self.assertEqual(tool("cidr", "10.0.0.0").returncode, 2)
        self.assertEqual(tool("cidr", "10.0.0.0/24", "3").returncode, 1)
        self.assertEqual(tool("cidr", "10.0.0.0/31", "4").returncode, 1)


class LpmTest(unittest.TestCase):
    TABLE = os.path.join(HERE, "table.txt")

    def winner(self, address, table=None):
        out = tool("lpm", table or self.TABLE, address).stdout
        lines = [line for line in out.splitlines() if "gana" in line or "sin ruta" in line]
        return lines[0].split()[-1]

    def test_chapter_example(self):
        self.assertEqual(self.winner("10.200.5.9"), "C")
        self.assertEqual(self.winner("10.200.6.1"), "B")
        self.assertEqual(self.winner("10.200.9.1"), "A")

    def test_default_route(self):
        self.assertEqual(self.winner("100.64.9.10"), "D")

    def test_order_does_not_matter(self):
        with open(self.TABLE, encoding="utf-8") as file:
            lines = file.read().splitlines()
        with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as other:
            other.write("\n".join(reversed(lines)) + "\n")
        try:
            self.assertEqual(self.winner("10.200.5.9", other.name), "C")
        finally:
            os.unlink(other.name)

    def test_no_route_without_default(self):
        with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as other:
            other.write("10.0.0.0/8 A\n")
        try:
            self.assertIn("sin ruta", tool("lpm", other.name, "11.0.0.1").stdout)
        finally:
            os.unlink(other.name)

    def test_rejects_prefix_with_host_bits(self):
        with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as other:
            other.write("10.1.2.3/24 A\n")
        try:
            self.assertEqual(tool("lpm", other.name, "10.1.2.3").returncode, 2)
        finally:
            os.unlink(other.name)


class HeaderTest(unittest.TestCase):
    def test_fields_of_captured_packet(self):
        out = tool("ipv4_header", stdin=CAPTURED).stdout
        self.assertRegex(out, r"TTL\s+64")
        self.assertRegex(out, r"DF=1 MF=0")
        self.assertRegex(out, r"largo total\s+84 bytes")
        self.assertRegex(out, r"protocolo\s+1 \(ICMP\)")
        self.assertRegex(out, r"origen\s+10\.200\.1\.10")
        self.assertRegex(out, r"destino\s+10\.200\.3\.10")

    def test_rejects_short_and_non_ipv4(self):
        self.assertEqual(tool("ipv4_header", stdin="\t0x0000:  4500 0054\n").returncode, 1)
        ipv6 = "\t0x0000:  6000 0000 0040 3a40 2001 0db8 0001 0000\n" \
               "\t0x0010:  0000 0000 0000 0010 2001 0db8 0001 0000\n"
        self.assertEqual(tool("ipv4_header", stdin=ipv6).returncode, 1)

    def test_checksum_changes_by_one_hundred_hex_when_ttl_drops(self):
        before = bytes.fromhex("4500005473b540004001" "ad50" "0ac8010a0ac8030a")
        after = bytes.fromhex("4500005473b540003f01" "ae50" "0ac8010a0ac8030a")
        # Un encabezado correcto, con su checksum incluido, suma 0xffff: el
        # complemento da 0.
        self.assertEqual(internet_checksum(before), 0)
        self.assertEqual(internet_checksum(after), 0)
        self.assertEqual(0xae50 - 0xad50, 0x100)


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