#include "core/logger.h"

#include <chrono>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <mutex>
#include <string>

namespace fn_log {

namespace {

std::mutex   g_mu;
FILE*        g_file       = nullptr;
std::string  g_path;
Level        g_min_level  = Level::Info;

// Ring buffer in-memory. g_count es el numero de entradas vivas (clamp a
// capacity); g_head es el indice donde se escribira la proxima entrada.
Entry        g_buf[kBufferCapacity];
std::size_t  g_count = 0;
std::size_t  g_head  = 0;

long long now_ms() {
    using namespace std::chrono;
    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}

// Formato: "YYYY-MM-DD HH:MM:SS.mmm". out debe tener al menos 24 bytes.
void format_ts(long long ts_ms, char* out, std::size_t out_size) {
    std::time_t secs   = static_cast<std::time_t>(ts_ms / 1000);
    int         millis = static_cast<int>(ts_ms % 1000);
    if (millis < 0) millis = 0;
    if (millis > 999) millis = 999;
    std::tm     tm_buf{};
#ifdef _WIN32
    localtime_s(&tm_buf, &secs);
#else
    localtime_r(&secs, &tm_buf);
#endif
    std::snprintf(out, out_size, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
                  (tm_buf.tm_year + 1900) % 10000,
                  (tm_buf.tm_mon + 1) % 100,
                  tm_buf.tm_mday % 100,
                  tm_buf.tm_hour % 100,
                  tm_buf.tm_min % 100,
                  tm_buf.tm_sec % 100,
                  millis);
}

void push_entry(Level level, long long ts_ms, const char* text) {
    Entry& e = g_buf[g_head];
    e.level = level;
    e.ts_ms = ts_ms;
    std::snprintf(e.text, kEntryTextMax, "%s", text);
    g_head = (g_head + 1) % kBufferCapacity;
    if (g_count < kBufferCapacity) ++g_count;
}

// Convierte el indice "logico" (0 = mas antigua) al indice fisico del array.
std::size_t logical_to_physical(std::size_t i) {
    if (g_count < kBufferCapacity) return i; // buffer aun no lleno
    return (g_head + i) % kBufferCapacity;
}

void emit(Level level, const char* fmt, std::va_list ap) {
    if (static_cast<int>(level) < static_cast<int>(g_min_level)) return;

    // msg deja 64 bytes para que el prefijo "[ts] [LEVEL] " quepa siempre en
    // el buffer destino sin que -Wformat-truncation se queje.
    char msg[kEntryTextMax - 64];
    std::vsnprintf(msg, sizeof(msg), fmt, ap);

    long long ts = now_ms();
    char ts_buf[32];
    format_ts(ts, ts_buf, sizeof(ts_buf));

    char line[kEntryTextMax];
    std::snprintf(line, sizeof(line), "[%s] [%s] %s",
                  ts_buf, level_label(level), msg);

    std::lock_guard<std::mutex> lk(g_mu);
    push_entry(level, ts, line);
    if (g_file) {
        std::fputs(line, g_file);
        std::fputc('\n', g_file);
        std::fflush(g_file);
    }
}

} // namespace

bool logger_init(const char* file_path, Level min_level) {
    std::lock_guard<std::mutex> lk(g_mu);
    if (g_file) {
        std::fclose(g_file);
        g_file = nullptr;
    }
    g_min_level = min_level;
    g_path.clear();
    if (!file_path || !*file_path) return false;

    g_file = std::fopen(file_path, "a");
    if (!g_file) {
        std::fprintf(stderr, "[fn_log] no pude abrir %s para append\n", file_path);
        return false;
    }
    g_path = file_path;
    return true;
}

void logger_close() {
    std::lock_guard<std::mutex> lk(g_mu);
    if (g_file) {
        std::fclose(g_file);
        g_file = nullptr;
    }
    g_path.clear();
}

void  logger_set_level(Level level) { std::lock_guard<std::mutex> lk(g_mu); g_min_level = level; }
Level logger_level()                { std::lock_guard<std::mutex> lk(g_mu); return g_min_level; }
const char* logger_path()           { return g_path.c_str(); }

void log_debug(const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Debug, fmt, ap); va_end(ap); }
void log_info (const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Info,  fmt, ap); va_end(ap); }
void log_warn (const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Warn,  fmt, ap); va_end(ap); }
void log_error(const char* fmt, ...) { std::va_list ap; va_start(ap, fmt); emit(Level::Error, fmt, ap); va_end(ap); }

std::size_t buffer_size() { std::lock_guard<std::mutex> lk(g_mu); return g_count; }

const Entry* buffer_at(std::size_t i) {
    std::lock_guard<std::mutex> lk(g_mu);
    if (i >= g_count) return nullptr;
    return &g_buf[logical_to_physical(i)];
}

void buffer_clear() {
    std::lock_guard<std::mutex> lk(g_mu);
    g_count = 0;
    g_head  = 0;
}

const char* level_label(Level level) {
    switch (level) {
        case Level::Debug: return "DEBUG";
        case Level::Info:  return "INFO";
        case Level::Warn:  return "WARN";
        case Level::Error: return "ERROR";
    }
    return "?";
}

} // namespace fn_log
