/*
 * SPDX-FileCopyrightText: 2025 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#include <tango/common/telemetry/TelemetryAppender.h>

#include <tango/internal/telemetry/InterfaceImplementation.h>
#include <tango/internal/telemetry/StdUniqueToNostdShared.h>

#include <tango/common/defines.h>
#include <tango/common/versions.h>

#include <opentelemetry/exporters/ostream/log_record_exporter_factory.h>
#include <opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_factory.h>
#include <opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_options.h>
#include <opentelemetry/exporters/otlp/otlp_http_log_record_exporter_factory.h>
#include <opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h>
#include <opentelemetry/sdk/logs/batch_log_record_processor_factory.h>
#include <opentelemetry/sdk/logs/batch_log_record_processor_options.h>
#include <opentelemetry/sdk/logs/logger_provider.h>
#include <opentelemetry/sdk/logs/logger_provider_factory.h>
#include <opentelemetry/sdk/logs/processor.h>
#include <opentelemetry/sdk/logs/simple_log_record_processor_factory.h>

namespace Tango::telemetry
{

//-----------------------------------------------------------------------------------------
// APPENDER: routes the Tango logs to the telemetry backend
//-----------------------------------------------------------------------------------------
void TelemetryAppender::init_logger_provider()
{
    // Don't engage with the telemetry system if not enabled
    if(!iface->cfg.enabled)
    {
        return;
    }
    if(!iface->cfg.logging_enabled)
    {
        cleanup_logger_provider();
        return;
    }

    std::vector<std::unique_ptr<opentelemetry::sdk::logs::LogRecordProcessor>> processorList;

    for(auto const &collector : iface->cfg.get_logging_collectors())
    {
        std::unique_ptr<opentelemetry::sdk::logs::LogRecordExporter> exporter;
        std::unique_ptr<opentelemetry::sdk::logs::LogRecordProcessor> processor;

        switch(collector.exporter_type)
        {
        case Configuration::Exporter::grpc:
#ifdef TANGO_TELEMETRY_USE_GRPC
        {
            opentelemetry::exporter::otlp::OtlpGrpcLogRecordExporterOptions opts;
            opts.endpoint = iface->cfg.extract_grpc_host_port(collector.endpoint);
            opts.use_ssl_credentials = false;
            exporter = opentelemetry::exporter::otlp::OtlpGrpcLogRecordExporterFactory::Create(opts);
        }
#endif
        break;
        case Configuration::Exporter::http:
#ifdef TANGO_TELEMETRY_USE_HTTP
        {
            opentelemetry::exporter::otlp::OtlpHttpLogRecordExporterOptions opts;
            opts.url = collector.endpoint;
            exporter = opentelemetry::exporter::otlp::OtlpHttpLogRecordExporterFactory::Create(opts);
        }
#endif
        break;
        case Configuration::Exporter::console:
            if(collector.endpoint == "cout")
            {
                exporter = opentelemetry::exporter::logs::OStreamLogRecordExporterFactory::Create(std::cout);
            }
            else if(collector.endpoint == "cerr")
            {
                exporter = opentelemetry::exporter::logs::OStreamLogRecordExporterFactory::Create(std::cerr);
            }
            else
            {
                TANGO_ASSERT(false);
            }
            break;
        default:
            TANGO_ASSERT_ON_DEFAULT(collector.exporter_type);
        }

        TANGO_ASSERT(exporter);

        switch(collector.exporter_type)
        {
        case Configuration::Exporter::grpc:
        case Configuration::Exporter::http:
        {
            opentelemetry::sdk::logs::BatchLogRecordProcessorOptions opts;
            opts.max_queue_size = iface->cfg.max_batch_queue_size;
            opts.max_export_batch_size = iface->cfg.logs_batch_size;
            opts.schedule_delay_millis = std::chrono::milliseconds(iface->cfg.batch_schedule_delay_in_milliseconds);
            processor = opentelemetry::sdk::logs::BatchLogRecordProcessorFactory::Create(std::move(exporter), opts);
        }
        break;
        case Configuration::Exporter::console:
            // fix garbeled output with batch processing
            processor = opentelemetry::sdk::logs::SimpleLogRecordProcessorFactory::Create(std::move(exporter));
            break;
        default:
            TANGO_ASSERT_ON_DEFAULT(collector.exporter_type);
        }
        TANGO_ASSERT(processor);
        processorList.push_back(std::move(processor));
    }

    if(iface->cfg.is_a(Configuration::Kind::Server))
    {
        const Configuration::Server &srv_info = std::get<0>(iface->cfg.details);
        logger_name = srv_info.device_name;
    }
    else
    {
        const Configuration::Client &clt_info = std::get<1>(iface->cfg.details);
        logger_name = clt_info.name;
    }

    auto resource = opentelemetry::sdk::resource::Resource::Create(iface->get_resource_attributes());

    /* The following code does not compile with gcc 10.2.1
    using LoggerProvider_SharedPtr = opentelemetry::nostd::shared_ptr<opentelemetry::sdk::logs::LoggerProvider>;
    interface->logger_provider = LoggerProvider_SharedPtr(
        opentelemetry::sdk::logs::LoggerProviderFactory::Create(std::move(processorList), resource));
    */
    iface->logger_provider = StdUniqueToNostdShared(
        opentelemetry::sdk::logs::LoggerProviderFactory::Create(std::move(processorList), resource));

    // set the global logger provider
    opentelemetry::logs::Provider::SetLoggerProvider(iface->logger_provider);
}

//-------------------------------------------------------------------------------------
// Appender::cleanup_logger_provider
//-------------------------------------------------------------------------------------
void TelemetryAppender::cleanup_logger_provider()
{
    auto old_provider = opentelemetry::logs::Provider::GetLoggerProvider();
    auto *base_class = dynamic_cast<opentelemetry::sdk::logs::LoggerProvider *>(old_provider.get());
    if(base_class != nullptr)
    {
        base_class->ForceFlush();
    }

    using LoggerProviderPtr = opentelemetry::nostd::shared_ptr<opentelemetry::logs::LoggerProvider>;
    iface->logger_provider = LoggerProviderPtr(new opentelemetry::logs::NoopLoggerProvider);
    opentelemetry::logs::Provider::SetLoggerProvider(iface->logger_provider);
}

//-------------------------------------------------------------------------------------
// Appender::_append
//-------------------------------------------------------------------------------------
int TelemetryAppender::_append(const log4tango::LoggingEvent &event)
{
    if(!iface->cfg.enabled)
    {
        return 0;
    }
    auto to_opentelemetry_level = [](const log4tango::Level::Value &level)
    {
        switch(level)
        {
        case log4tango::Level::FATAL:
            return opentelemetry::logs::Severity::kFatal;
        case log4tango::Level::ERROR:
            return opentelemetry::logs::Severity::kError;
        case log4tango::Level::WARN:
            return opentelemetry::logs::Severity::kWarn;
        case log4tango::Level::INFO:
            return opentelemetry::logs::Severity::kInfo;
        case log4tango::Level::DEBUG:
            return opentelemetry::logs::Severity::kDebug;
        case log4tango::Level::OFF:
        default:
            return opentelemetry::logs::Severity::kInvalid;
        }
    };

    auto ctx = iface->get_current_opentelemetry_span()->GetContext();
    auto provider = opentelemetry::logs::Provider::GetLoggerProvider();
    auto logger = provider->GetLogger(logger_name, "cppTango", git_revision());

    logger->EmitLogRecord(to_opentelemetry_level(event.level),
                          event.message,
                          ctx.trace_id(),
                          ctx.span_id(),
                          ctx.trace_flags(),
                          opentelemetry::common::SystemTimestamp(event.timestamp),
                          opentelemetry::common::MakeAttributes(
                              {{"code.filepath", event.file_path}, {"code.lineno", event.line_number}}));

    return 0;
}

} // namespace Tango::telemetry
