// Pruebas públicas de los pasos 3 y 4: la clase hace lo mismo que el módulo en C.
#include <cassert>
#include <cstddef>
#include <iostream>
#include <random>
#include <string>
#include <utility>
#include <vector>

#include "grade_book.h"

void test_starts_empty() {
    const GradeBook book{};
    assert(book.size() == 0);
    assert(book.grade_of("Ana") == -1);
}

void test_add_and_get() {
    GradeBook book;
    assert(book.add("Ana", 14));
    assert(book.add("Luis", 9));
    assert(book.size() == 2);
    assert(book.grade_of("Ana") == 14);
    assert(book.grade_of("Luis") == 9);
    assert(book.name_at(0) == "Ana");
    assert(book.name_at(1) == "Luis");
}

void test_rejects_invalid() {
    GradeBook book;
    assert(!book.add("Ana", -1));
    assert(!book.add("Ana", 21));
    assert(!book.add("", 10));
    assert(book.size() == 0);
    assert(book.add("Ana", 0));
    assert(book.add("Rosa", 20));
    assert(!book.add("Ana", 20));
    assert(book.grade_of("Ana") == 0);
    assert(book.size() == 2);
}

void test_set_grade() {
    GradeBook book;
    assert(book.add("Ana", 14));
    assert(book.set_grade("Ana", 20));
    assert(book.grade_of("Ana") == 20);
    assert(!book.set_grade("Ana", 21));
    assert(!book.set_grade("Ana", -1));
    assert(book.grade_of("Ana") == 20);
    assert(!book.set_grade("Luis", 10));
    assert(book.size() == 1);
}

void test_remove_keeps_order() {
    GradeBook book;
    assert(book.add("Ana", 14));
    assert(book.add("Luis", 9));
    assert(book.add("Rosa", 17));
    assert(book.remove("Luis"));
    assert(!book.remove("Luis"));
    assert(book.size() == 2);
    assert(book.name_at(0) == "Ana");
    assert(book.name_at(1) == "Rosa");
    assert(book.remove("Rosa"));
    assert(book.remove("Ana"));
    assert(book.size() == 0);
    assert(book.add("Luis", 3));
    assert(book.grade_of("Luis") == 3);
}

void test_keeps_its_own_copy_of_the_name() {
    GradeBook book;
    std::string name = "Ana";
    assert(book.add(name, 14));
    name[0] = 'E';
    assert(book.grade_of("Ana") == 14);
    assert(book.grade_of("Ena") == -1);
}

void test_copy_is_independent() {
    GradeBook book;
    assert(book.add("Ana", 14));
    GradeBook other = book;
    assert(other.set_grade("Ana", 5));
    assert(other.add("Luis", 9));
    assert(book.grade_of("Ana") == 14);
    assert(book.size() == 1);
}

// Devuelve la posición de name en la referencia, o reference.size() si no está.
std::size_t find_in(const std::vector<std::pair<std::string, int>>& reference, const std::string& name) {
    std::size_t i = 0;
    while (i < reference.size() && reference[i].first != name) {
        i++;
    }
    return i;
}

// Operaciones al azar, con semilla fija, comparadas contra un std::vector de pares.
void test_against_reference() {
    std::mt19937 random(2026);
    GradeBook book;
    std::vector<std::pair<std::string, int>> reference;
    for (int step = 0; step < 3000; step++) {
        std::string name = "e" + std::to_string(random() % 30);
        int grade = static_cast<int>(random() % 25) - 2;
        bool valid = grade >= 0 && grade <= 20;
        std::size_t place = find_in(reference, name);
        bool present = place < reference.size();
        switch (random() % 4) {
            case 0:
                assert(book.add(name, grade) == (valid && !present));
                if (valid && !present) {
                    reference.push_back({name, grade});
                }
                break;
            case 1:
                assert(book.set_grade(name, grade) == (valid && present));
                if (valid && present) {
                    reference[place].second = grade;
                }
                break;
            case 2:
                assert(book.remove(name) == present);
                if (present) {
                    reference.erase(reference.begin() + place);
                }
                break;
            default:
                assert(book.grade_of(name) == (present ? reference[place].second : -1));
        }
        assert(book.size() == reference.size());
        for (std::size_t i = 0; i < reference.size(); i++) {
            assert(book.name_at(i) == reference[i].first);
        }
    }
}

int main() {
    test_starts_empty();
    test_add_and_get();
    test_rejects_invalid();
    test_set_grade();
    test_remove_keeps_order();
    test_keeps_its_own_copy_of_the_name();
    test_copy_is_independent();
    test_against_reference();
    std::cout << "pasos 3 y 4: todas las pruebas pasaron\n";
    return 0;
}
