"""Corre los ejemplos de la semana y comprueba lo que se puede contar: nunca tiempos."""
import os
import pathlib
import subprocess
import unittest

HERE = pathlib.Path(__file__).parent
DATA = os.environ.get("DATA", "../../data")
WORDS = f"{DATA}/palabras.txt"
SAN = ["g++", "-Wall", "-Wextra", "-std=c++20", "-g", "-fsanitize=address,undefined"]


def run(*command):
    return subprocess.run(
        list(command), cwd=HERE, text=True, encoding="utf-8", errors="replace", stdin=subprocess.DEVNULL,
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=300,
    )


class Examples(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # Se corre una sola vez: tarda unos segundos, y varias pruebas leen su salida.
        cls.height_lines = run("build/height_table").stdout.splitlines()

    def test_structure_tests_pass(self):
        done = run("build/test_structures")
        self.assertEqual(done.returncode, 0, done.stdout)

    def test_programs_exit_cleanly(self):
        for name in ["tree_demo", "erase_demo", "insertion_orders", "query_demo", "map_queries", "map_guarantees"]:
            with self.subTest(program=name):
                done = run(f"build/{name}")
                self.assertEqual(done.returncode, 0, done.stdout)

    def test_longest_chain_has_32_orders(self):
        self.assertIn("altura 5: 32 ordenes", run("build/insertion_orders").stdout)

    def test_sorted_comparisons_are_exact(self):
        """Las dos últimas columnas son el conteo real y la fórmula."""
        lines = self.height_lines
        rows = lines[lines.index("Claves ordenadas") + 2:]
        self.assertEqual(len(rows), 5)
        for row in rows:
            n, height, comparisons, formula = row.split()
            self.assertEqual(int(height), int(n) - 1)
            self.assertEqual(comparisons, formula)

    def test_random_height_is_far_from_n(self):
        lines = self.height_lines
        for row in lines[2:lines.index("Claves ordenadas")]:
            fields = row.split()
            n, log_n = int(fields[0]), float(fields[1])
            for height in fields[2:5]:
                self.assertLess(int(height), 4 * log_n)
                self.assertLess(int(height), n)

    def test_dictionary_shape_is_counted(self):
        lines = run("build/dictionary_order", WORDS).stdout.splitlines()
        self.assertEqual(lines[0], "orden de bytes: si")
        self.assertIn("20000 palabras, altura 19999,", lines[1])
        self.assertIn("20000 palabras, altura 31,", lines[2])
        self.assertIn("1045757 palabras, altura 49,", lines[3])

    def test_map_keeps_pointer_and_counts_nodes(self):
        lines = run("build/map_guarantees").stdout.splitlines()
        self.assertEqual(lines[1], "reservas de std::map al insertar 1000 claves: 1000")



class DeliberateErrors(unittest.TestCase):
    def build(self, name):
        binary = f"build/{name}"
        done = run(*SAN, f"errors/{name}.cpp", "-o", binary)
        self.assertEqual(done.returncode, 0, done.stdout)
        return binary

    def test_recursive_destroy_overflows(self):
        binary = self.build("recursive_destroy")
        self.assertEqual(run(binary, "1000").returncode, 0)
        done = run(binary, "1000000")
        self.assertNotEqual(done.returncode, 0)
        self.assertIn("stack-overflow", done.stdout)

    def test_forgot_successor_is_found_by_invariants(self):
        done = run(self.build("forgot_successor"))
        self.assertNotEqual(done.returncode, 0)
        self.assertIn("1 3 4 7 7 8", done.stdout)
        self.assertIn("count_checked", done.stdout)
        self.assertNotIn("AddressSanitizer", done.stdout)

    def test_pointer_after_erase(self):
        done = run(self.build("pointer_after_erase"))
        self.assertNotEqual(done.returncode, 0)
        self.assertIn("heap-use-after-free", done.stdout)
        self.assertIn("sigue el 7: 1", done.stdout)

    def test_local_check_misses_the_break(self):
        done = run(self.build("local_check"))
        self.assertEqual(done.returncode, 0, done.stdout)
        self.assertIn("todo bien", done.stdout)
        self.assertIn("contains(7): no", done.stdout)


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