#include <doctest/doctest.h>

#include "fgc/Config.h"

#include <cmath>
#include <cstdlib>

using namespace fgc;

TEST_CASE("ConfigLoader maps and defaults typed values") {
    unsetenv("FGC_MQTT_USER");
    unsetenv("FGC_MQTT_PW");

    std::map<std::string, std::string> kv = {
        {"General.tower_name", "Staeffelsberg"},
        {"General.image_interval", "3"},
        {"General.debug", "1"},
        {"Network.zkms_server_ip", "10.0.0.5"},
        {"Network.mqtt_user", "fileuser"},
        {"Camera.id_Cam1", "DEV_1"},
        {"Camera.id_Cam3", "DEV_3"},
        {"Features.enable_mqtt", "false"},
    };
    AppConfig c = ConfigLoader::fromMap(kv);

    CHECK(c.general.tower_name == "Staeffelsberg");
    CHECK(c.general.image_interval == 3);
    CHECK(c.general.debug == true);
    CHECK(c.network.broker_ip == "10.0.0.5");
    CHECK(c.network.mqtt_user == "fileuser");
    CHECK(c.camera.ids.size() == 2);          // blank id_Cam2 skipped
    CHECK(c.camera.ids[1] == "DEV_3");
    CHECK(c.features.enable_mqtt == false);
    CHECK(!c.paths.output_dir.empty());        // defaulted
    CHECK(std::abs(c.image_rate() - 1.0 / 3.0) < 1e-9);
}

TEST_CASE("environment overrides file credentials") {
    setenv("FGC_MQTT_USER", "envuser", 1);
    AppConfig c = ConfigLoader::fromMap({{"Network.mqtt_user", "fileuser"}});
    CHECK(c.network.mqtt_user == "envuser");
    unsetenv("FGC_MQTT_USER");
}

TEST_CASE("ConfigLoader validates input") {
    CHECK_THROWS(ConfigLoader::fromMap({{"General.image_interval", "0"}}));
    CHECK_THROWS(ConfigLoader::fromMap({{"General.image_interval", "abc"}}));
    CHECK_THROWS(ConfigLoader::fromMap({{"General.debug", "maybe"}}));
}

TEST_CASE("updateIniSectionKeys replaces in-section keys, leaving others intact") {
    const std::string in =
        "[General]\n"
        "tower_name = Foo\n"
        "[Motor]\n"
        "; calibrate me\n"
        "yaw_counts_per_deg = 983.33\n"
        "yaw_zero_count = 500000\n"
        "yaw_min_deg = -90\n"
        "[Scan]\n"
        "yaw_min_deg = -90\n";  // same key name, different section — must NOT change

    auto out = updateIniSectionKeys(in, "Motor",
                                    {{"yaw_counts_per_deg", "1000"}, {"yaw_zero_count", "12345"}});

    CHECK(out.find("yaw_counts_per_deg = 1000") != std::string::npos);
    CHECK(out.find("yaw_zero_count = 12345") != std::string::npos);
    CHECK(out.find("yaw_counts_per_deg = 983.33") == std::string::npos);  // replaced
    CHECK(out.find("; calibrate me") != std::string::npos);               // comment kept
    CHECK(out.find("tower_name = Foo") != std::string::npos);             // other section kept
    CHECK(out.find("[Motor]\nyaw_min_deg") == std::string::npos);         // Motor's other key kept in place
    CHECK(out.find("[Scan]\nyaw_min_deg = -90") != std::string::npos);    // Scan untouched
}

TEST_CASE("updateIniSectionKeys appends keys missing from the section") {
    const std::string in = "[Motor]\nyaw_counts_per_deg = 1\n";
    auto out = updateIniSectionKeys(in, "Motor", {{"pitch_zero_count", "7"}});
    // re-parsing must see the appended key under [Motor]
    CHECK(out.find("pitch_zero_count = 7") != std::string::npos);
}
