#include <doctest/doctest.h>

#include "fgc/Logger.h"

#include <iostream>
#include <sstream>
#include <string>

using namespace fgc;

namespace {

// Run a callable with std::cout redirected to a buffer; return what was written.
template <typename F>
std::string captureCout(F&& f) {
    std::ostringstream oss;
    std::streambuf* old = std::cout.rdbuf(oss.rdbuf());
    f();
    std::cout.rdbuf(old);
    return oss.str();
}

unsigned bits(LogCat c) { return static_cast<unsigned>(c); }

}  // namespace

TEST_CASE("parseCategories maps names, all/none, and flags unknown tokens") {
    bool ok = true;

    CHECK(Logger::parseCategories("serial,mqtt", &ok) == (bits(LogCat::Serial) | bits(LogCat::Mqtt)));
    CHECK(ok);

    CHECK(Logger::parseCategories("all", &ok) == bits(LogCat::All));
    CHECK(ok);

    CHECK(Logger::parseCategories("none", &ok) == 0u);
    CHECK(ok);
    CHECK(Logger::parseCategories("", &ok) == 0u);
    CHECK(ok);

    // Case- and whitespace-insensitive.
    CHECK(Logger::parseCategories("  SeRiAl , Camera ", &ok)
          == (bits(LogCat::Serial) | bits(LogCat::Camera)));
    CHECK(ok);

    // Unknown token: ok=false, but recognized tokens still apply.
    ok = true;
    CHECK(Logger::parseCategories("serial,bogus", &ok) == bits(LogCat::Serial));
    CHECK_FALSE(ok);
}

TEST_CASE("catFromString round-trips and flags unknown") {
    bool ok = true;
    CHECK(Logger::catFromString("control", &ok) == LogCat::Control);
    CHECK(ok);
    CHECK(Logger::catFromString("nope", &ok) == LogCat::None);
    CHECK_FALSE(ok);
}

TEST_CASE("category enable/disable/set round-trip") {
    Logger::setCategories(0);
    CHECK(Logger::categories() == 0u);
    CHECK_FALSE(Logger::categoryEnabled(LogCat::Serial));

    Logger::enableCategory(LogCat::Serial);
    Logger::enableCategory(LogCat::Mqtt);
    CHECK(Logger::categoryEnabled(LogCat::Serial));
    CHECK(Logger::categoryEnabled(LogCat::Mqtt));
    CHECK_FALSE(Logger::categoryEnabled(LogCat::Camera));

    Logger::disableCategory(LogCat::Serial);
    CHECK_FALSE(Logger::categoryEnabled(LogCat::Serial));
    CHECK(Logger::categoryEnabled(LogCat::Mqtt));

    Logger::setCategories(bits(LogCat::All));
    CHECK(Logger::categoryEnabled(LogCat::Camera));
    CHECK(Logger::categoryEnabled(LogCat::Control));

    Logger::setCategories(0);  // leave global state clean for other tests
}

TEST_CASE("LOG_TRACE_CAT gating is independent of the linear level") {
    Logger::setCategories(0);

    SUBCASE("suppressed when category off (even at trace level)") {
        Logger::setLevel(LogLevel::Trace);
        std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Serial) << "TX kd180"; });
        CHECK(out.empty());
    }

    SUBCASE("emitted with [SERIAL] tag when category on, at default info level") {
        Logger::setLevel(LogLevel::Info);  // NOT trace
        Logger::enableCategory(LogCat::Serial);
        std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Serial) << "TX kd180"; });
        CHECK(out.find("[SERIAL]") != std::string::npos);
        CHECK(out.find("TX kd180") != std::string::npos);
    }

    SUBCASE("a different category stays silent") {
        Logger::setLevel(LogLevel::Info);
        Logger::enableCategory(LogCat::Serial);  // serial on, mqtt off
        std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Mqtt) << "PUB t p"; });
        CHECK(out.empty());
    }

    SUBCASE("level Off silences categories too") {
        Logger::setLevel(LogLevel::Off);
        Logger::enableCategory(LogCat::Serial);
        std::string out = captureCout([] { LOG_TRACE_CAT(LogCat::Serial) << "TX x"; });
        CHECK(out.empty());
    }

    // Restore defaults so test ordering can't leak state.
    Logger::setCategories(0);
    Logger::setLevel(LogLevel::Info);
}
