#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <iostream>
#include "core/GravityManager.h"

TEST_CASE("GravityManager basic ms calculation", "[gravity]") {
    GravityManager g;
    g.setGlobalMultiplier(1.0);
    double ms0 = g.getMsForLevel(0);
    REQUIRE(ms0 > 0);
    double ms29 = g.getMsForLevel(29);
    REQUIRE(ms29 >= 1.0);
}

// Additional tests
TEST_CASE("frames_to_ms_exact", "[gravity][exact]") {
    // Reuse the reference constants (must match GravityManager implementation)
    constexpr double NES_FPS = 60.0988;
    constexpr double FRAME_MS = 1000.0 / NES_FPS;
    constexpr int FRAMES_TABLE[30] = {
        48, 43, 38, 33, 28, 23, 18, 13, 8, 6,
        5, 5, 5, 4, 4, 4, 3, 3, 3, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 1
    };

    GravityManager g;
    g.setGlobalMultiplier(1.0);

    for (int lvl = 0; lvl <= 29; ++lvl) {
        double expected = static_cast<double>(FRAMES_TABLE[lvl]) * FRAME_MS * 1.0;
        double actual = g.getMsForLevel(lvl);
        // GravityManager clamps to minimum 1.0ms; expected for these levels is >> 1.0 except possibly level 29
        if (expected < 1.0) expected = 1.0;
        if (!(actual == Catch::Approx(expected).epsilon(1e-6))) {
            std::cerr << "DEBUG: lvl=" << lvl << ", levelMultiplier=" << g.getLevelMultiplier(lvl)
                      << ", FRAMES_TABLE[lvl]=" << FRAMES_TABLE[lvl]
                      << ", actual=" << actual << ", expected=" << expected << std::endl;
        }
        REQUIRE(actual == Catch::Approx(expected).epsilon(1e-6));
        REQUIRE(g.getFpsForLevel(lvl) == Catch::Approx(1000.0 / actual).epsilon(1e-6));
    }
}

TEST_CASE("multiplier_effects", "[gravity][multiplier]") {
    GravityManager g;
    g.setGlobalMultiplier(1.0);

    double base = g.getMsForLevel(5); // level 5 baseline
    g.setLevelMultiplier(5, 2.0);
    double doubled = g.getMsForLevel(5);
    REQUIRE(doubled == Catch::Approx(base * 2.0).epsilon(1e-6));

    // global multiplier scales all levels
    g.setGlobalMultiplier(0.5);
    double afterGlobal = g.getMsForLevel(5);
    REQUIRE(afterGlobal == Catch::Approx(doubled * 0.5).epsilon(1e-6));
}

TEST_CASE("clamping_and_edges", "[gravity][clamp]") {
    GravityManager g;
    // global multiplier clamped to [0.01,100.0]
    g.setGlobalMultiplier(0.0001);
    REQUIRE(g.getGlobalMultiplier() == Catch::Approx(0.01).epsilon(1e-12));
    g.setGlobalMultiplier(500.0);
    REQUIRE(g.getGlobalMultiplier() == Catch::Approx(100.0).epsilon(1e-12));

    // per-level multiplier clamps
    g.setLevelMultiplier(100, 200.0);
    REQUIRE(g.getLevelMultiplier(100) == Catch::Approx(100.0).epsilon(1e-12));
    // negative level queries map to level 0
    REQUIRE(g.getLevelMultiplier(-5) == Catch::Approx(g.getLevelMultiplier(0)).epsilon(1e-12));
}

TEST_CASE("min_ms_enforced", "[gravity][min]") {
    GravityManager g;
    // Force very small multipliers then ensure minimum 1.0ms enforced
    g.setLevelMultiplier(29, 0.01);
    g.setGlobalMultiplier(0.01);
    double ms29 = g.getMsForLevel(29);
    REQUIRE(ms29 >= 1.0);
    REQUIRE(ms29 == Catch::Approx(1.0).epsilon(1e-12));
}
