/* Copyright (c) 2024 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#include <lua.hpp>
#include <mutex>
#include "opentelemetry/trace/trace_id.h"
#include "opentelemetry/trace/span_id.h"
#include "opentelemetry/trace/trace_flags.h"
#include "opentelemetry/logs/logger.h"
#include "opentelemetry/logs/provider.h"
#include "opentelemetry/sdk/logs/exporter.h"
#include "opentelemetry/sdk/logs/processor.h"
#include "opentelemetry/sdk/logs/logger_provider.h"
#include "opentelemetry/sdk/logs/logger_provider_factory.h"
#include "opentelemetry/sdk/logs/batch_log_record_processor_factory.h"
#include "opentelemetry/sdk/logs/batch_log_record_processor_options.h"
#if defined(BUILD_TYPE) && defined(BUILD_TYPE_DT) && BUILD_TYPE == BUILD_TYPE_DT
#include "opentelemetry/exporters/ostream/log_record_exporter_factory.h"
#include <fstream>
#endif
#include "opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_options.h"

#include "utils/utils.h"

namespace Otel {
namespace logs_sdk = opentelemetry::sdk::logs;
namespace logs_api = opentelemetry::logs;
namespace trace_api = opentelemetry::trace;
namespace otlp = opentelemetry::exporter::otlp;

class LuaOtelLoggerProvider
{
public:
    void InitLoggerProvider(int max_size=2048, int delay_millies=5000, int max_export_batch_size=512)
    {
        std::lock_guard<std::recursive_mutex> guard(m_mutex);
        if (delay_millies == m_delay_millies) {
            return;
        }
#if defined(BUILD_TYPE) && defined(BUILD_TYPE_DT) && BUILD_TYPE == BUILD_TYPE_DT
        Utils::initialize_telemetry();
        file.open("./otel_logs.txt");
        auto exporter = opentelemetry::exporter::logs::OStreamLogRecordExporterFactory::Create(file);
        logs_sdk::BatchLogRecordProcessorOptions options;
        auto processor = logs_sdk::BatchLogRecordProcessorFactory::Create(std::move(exporter), options);
        std::shared_ptr<logs_api::LoggerProvider> provider(logs_sdk::LoggerProviderFactory::Create(std::move(processor)));

        logs_api::Provider::SetLoggerProvider(provider);
#else
        Utils::initialize_telemetry();
        otlp::OtlpGrpcLogRecordExporterOptions opts;
        opts.endpoint = "localhost:44318";
        opts.use_ssl_credentials = false;
        auto exporter = otlp::OtlpGrpcLogRecordExporterFactory::Create(opts);
        logs_sdk::BatchLogRecordProcessorOptions options;
        options.max_queue_size = max_size;
        options.schedule_delay_millis = std::chrono::milliseconds(delay_millies);
        options.max_export_batch_size = max_export_batch_size;
        auto processor = logs_sdk::BatchLogRecordProcessorFactory::Create(std::move(exporter), options);
        std::shared_ptr<logs_api::LoggerProvider> provider(logs_sdk::LoggerProviderFactory::Create(std::move(processor)));
        logs_api::Provider::SetLoggerProvider(provider);
#endif
        m_delay_millies = delay_millies;
        m_is_init = true;
    }

    nostd::shared_ptr<logs_api::Logger> GetLogger(const std::string &name)
    {
        if (!m_is_init) {
            std::lock_guard<std::recursive_mutex> guard(m_mutex);
            if (!m_is_init) {
                InitLoggerProvider();
            }
        }
        return logs_api::Provider::GetLoggerProvider()->GetLogger(name);
    }

    void ForceFlush(std::chrono::microseconds timeout)
    {
        if (!m_is_init) {
            return;
        }
        static_cast<logs_sdk::LoggerProvider *>(logs_api::Provider::GetLoggerProvider().get())->ForceFlush(timeout);
    }

    void DeInitLoggerProvider()
    {
        if (!m_is_init) {
            return;
        }
        std::lock_guard<std::recursive_mutex> guard(m_mutex);
        if (!m_is_init) {
            return;
        }
        m_delay_millies = 0;
        std::shared_ptr<logs_api::LoggerProvider> none;
        logs_api::Provider::SetLoggerProvider(none);
        m_is_init = false;
    }

    ~LuaOtelLoggerProvider()
    {
#if defined(BUILD_TYPE) && defined(BUILD_TYPE_DT) && BUILD_TYPE == BUILD_TYPE_DT
        file.close();
#endif
    }

private:
    int m_delay_millies = 0;
    bool m_is_init = false;
    std::recursive_mutex m_mutex;
#if defined(BUILD_TYPE) && defined(BUILD_TYPE_DT) && BUILD_TYPE == BUILD_TYPE_DT
    std::ofstream file;
#endif
};

static LuaOtelLoggerProvider loggerProvider;

class LuaOtelLogger
{
public:
    LuaOtelLogger(const std::string &name)
    {
        m_logger = loggerProvider.GetLogger(name);
    }

    void Log(int severity, nostd::string_view body, const AttributeMap &attributes)
    {
        auto log_record = m_logger->CreateLogRecord();
        log_record->SetSeverity(static_cast<opentelemetry::logs::Severity>(severity));
        log_record->SetBody(body);
        for (const auto [key, value] : attributes) {
            if (key == "trace_id" && nostd::holds_alternative<nostd::string_view>(value)) {
                trace_api::TraceId trace_id(Utils::HexToBytes(nostd::get<nostd::string_view>(value)));
                log_record->SetTraceId(trace_id);
                continue;
            }
            if (key == "span_id" && nostd::holds_alternative<nostd::string_view>(value)) {
                trace_api::SpanId span_id(Utils::HexToBytes(nostd::get<nostd::string_view>(value)));
                log_record->SetSpanId(span_id);
                continue;
            }
            if (key == "trace_flags" && nostd::holds_alternative<int64_t>(value)) {
                trace_api::TraceFlags trace_flags(nostd::get<int64_t>(value));
                log_record->SetTraceFlags(trace_flags);
                continue;
            }
            log_record->SetAttribute(key, value);
        }
        m_logger->EmitLogRecord(std::move(log_record));
    }

    void ForceFlush(std::chrono::microseconds timeout = std::chrono::microseconds(0))
    {
        loggerProvider.ForceFlush(timeout);
    }

private:
    nostd::shared_ptr<logs_api::Logger> m_logger;
};

static int lua_log(lua_State *L)
{
    LuaOtelLogger *logger = reinterpret_cast<LuaOtelLogger *>(luaL_checkudata(L, 1, "LuaOtelLogger"));
    int severity = luaL_checkinteger(L, 2);
    size_t len = 0;
    const char *body = luaL_checklstring(L, 3, &len);

    AttributeMap attributes;
    if (lua_istable(L, 4)) {
        Utils::get_attributes(L, attributes, 4);
    }
    logger->Log(severity, nostd::string_view(body, len), attributes);
    return 0;
}

static int lua_flush(lua_State *L)
{
    LuaOtelLogger *logger = reinterpret_cast<LuaOtelLogger *>(luaL_checkudata(L, 1, "LuaOtelLogger"));
    if (lua_gettop(L) < 2) {
        logger->ForceFlush();
        return 0;
    }
    int timeout = luaL_checkinteger(L, 2);
    logger->ForceFlush(std::chrono::microseconds(timeout));
    return 0;
}

static int l_init_logger_provider(lua_State *L)
{
    // 最大队列大小,未指定使用默认值2048
    int max_queue_size = lua_isinteger(L, 1) ? static_cast<int32_t>(lua_tointeger(L, 1)) : 2048;

    // 发送间隔,未指定使用默认值5000ms
    int schedule_delay_millis = lua_isinteger(L, 2) ? static_cast<int32_t>(lua_tointeger(L, 2)) : 5000;

    // 每次导出的最大批次大小, 小于等于最大队列大小，未指定使用默认值512
    int max_export_batch_size = lua_isinteger(L, 3) ? static_cast<int32_t>(lua_tointeger(L, 3)) : 512;
    loggerProvider.InitLoggerProvider(max_queue_size, schedule_delay_millis, max_export_batch_size);
    return 0;
}

static int l_deinit_logger_provider(lua_State *L)
{
    loggerProvider.DeInitLoggerProvider();
    return 0;
}

static const luaL_Reg logger_methods[] = {{"log", lua_log}, {"flush", lua_flush}, {nullptr, nullptr}};
static int l_create_otel_logger(lua_State *L)
{
    const char *name = luaL_checkstring(L, 1);
    void *ud = lua_newuserdata(L, sizeof(LuaOtelLogger));
    new (ud) LuaOtelLogger(name);
    luaL_getmetatable(L, "LuaOtelLogger");
    lua_setmetatable(L, -2);
    return 1;
}

extern "C" int luaopen_otel_logs(lua_State *L)
{
    luaL_checkversion(L);
    luaL_newmetatable(L, "LuaOtelLogger");
    luaL_setfuncs(L, logger_methods, 0);
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");

    luaL_Reg lib[] = {
        {"create_otel_logger", l_create_otel_logger},
        {"init_logger_provider", l_init_logger_provider},
        {"deinit_logger_provider", l_deinit_logger_provider},
        {nullptr, nullptr}
    };
    luaL_newlib(L, lib);
    return 1;
}
} // namespace OTEL