// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "linyaps_box/log/formatter.h"

#include "linyaps_box/log/utils.h"
#include "linyaps_box/os/result.h"
#include "linyaps_box/utils/date.h"

#include <fmt/format.h>
#include <nlohmann/json.hpp>

#include <stdexcept>
#include <string_view>

namespace linyaps_box::log {

auto to_json(nlohmann::json &j, level lvl) -> void
{
    switch (lvl) {
    case level::fatal:
        j = "FATAL";
        break;
    case level::error:
        j = "ERROR";
        break;
    case level::warn:
        j = "WARN";
        break;
    case level::info:
        j = "INFO";
        break;
    case level::debug:
        j = "DEBUG";
        break;
    }
}

auto from_json(const nlohmann::json &j, level &lvl) -> void
{
    auto s = j.get<std::string_view>();
    if (s == "FATAL") {
        lvl = level::fatal;
        return;
    }

    if (s == "ERROR") {
        lvl = level::error;
        return;
    }

    if (s == "WARN") {
        lvl = level::warn;
        return;
    }

    if (s == "INFO") {
        lvl = level::info;
        return;
    }

    if (s == "DEBUG") {
        lvl = level::debug;
        return;
    }

    throw nlohmann::json::other_error::create(500,
                                              std::string("unknown log level: ").append(s),
                                              &j);
}

namespace detail {
struct oci_log_message
{
    std::string_view msg;
    static constexpr std::string_view base_indent = "    ";
};
} // namespace detail

auto to_json(nlohmann::json &j, const log_context &ctx) noexcept -> void
{
    std::array<char, 30> time_buf{ };
    auto len =
      linyaps_box::utils::to_created_time(linyaps_box::utils::span{ time_buf },
                                          std::chrono::system_clock::time_point{ ctx.time },
                                          linyaps_box::utils::subsecond_precision::nanoseconds);
    std::string_view time_str{ time_buf.begin(), len };

    j = nlohmann::json{
        { "time", time_str },
        { "level", ctx.lvl },
        { "pid", ctx.pid },
        { "message", ctx.message },
    };
#ifdef LINYAPS_BOX_LOG_ENABLE_SOURCE_LOCATION
    j["file"] = ctx.file;
    j["line"] = ctx.line;
    j["function"] = ctx.function;
#endif
    if (ctx.errno_ != 0) {
        j["error_code"] = ctx.errno_;
        j["error_message"] = os::make_error_code(ctx.errno_).message();
    }
}

auto format_text(fmt::memory_buffer &buf, const log_context &ctx, fmt::text_style style) -> void
{
    // Format time into a local buffer first to avoid writing raw bytes to output
    thread_local std::array<char, 30> time_buf{ };
    auto len =
      linyaps_box::utils::to_created_time(linyaps_box::utils::span{ time_buf },
                                          std::chrono::system_clock::time_point{ ctx.time },
                                          linyaps_box::utils::subsecond_precision::nanoseconds);

#ifdef LINYAPS_BOX_LOG_ENABLE_SOURCE_LOCATION
    fmt::format_to(std::back_inserter(buf),
                   style,
                   "[{}] [{:<5}] [{}] [{}:{} {}]:\n{}",
                   std::string_view{ time_buf.data(), len },
                   level_name(ctx.lvl),
                   ctx.pid,
                   ctx.file,
                   ctx.line,
                   ctx.function,
                   detail::oci_log_message{ ctx.message });
#else
    fmt::format_to(std::back_inserter(buf),
                   style,
                   "[{}] [{:<5}] [{}]:\n{}",
                   std::string_view{ time_buf, len },
                   level_name(ctx.lvl),
                   ctx.pid,
                   detail::oci_log_message{ ctx.message });
#endif
    if (ctx.errno_ != 0) {
        fmt::format_to(std::back_inserter(buf), "\n{}", detail::oci_log_message::base_indent);
        fmt::format_to(std::back_inserter(buf), "{}", os::make_error_code(ctx.errno_).message());
    }

    buf.push_back('\n');
}

auto format_log(fmt::memory_buffer &buf,
                const log_context &ctx,
                output_format fmt,
                fmt::text_style style) noexcept -> void
{
    switch (fmt) {
    case output_format::text:
        format_text(buf, ctx, style);
        break;
    case output_format::json:
        fmt::format_to(std::back_inserter(buf), "{}\n", nlohmann::json(ctx).dump());
        break;
    }
}

} // namespace linyaps_box::log

template <>
struct fmt::formatter<linyaps_box::log::detail::oci_log_message>
{
    constexpr auto parse(fmt::format_parse_context &ctx)
    {
        const auto *it = ctx.begin();
        const auto *end = ctx.end();
        if (it != end && *it != '}') {
            throw fmt::format_error("oci_log_message does not accept format specs");
        }
        return it;
    }

    template <typename FormatContext>
    auto format(const linyaps_box::log::detail::oci_log_message &lm, FormatContext &ctx) const
    {
        auto out = ctx.out();
        auto text = lm.msg;

        while (!text.empty() && text.back() == '\n') {
            text.remove_suffix(1);
        }

        out = fmt::format_to(out, "{}", linyaps_box::log::detail::oci_log_message::base_indent);

        std::string_view::size_type start = 0;
        while (true) {
            auto pos = text.find('\n', start);
            if (pos == std::string_view::npos) {
                out = fmt::format_to(out, "{}", text.substr(start));
                break;
            }

            out = fmt::format_to(out,
                                 "{}\n{}",
                                 text.substr(start, pos - start),
                                 linyaps_box::log::detail::oci_log_message::base_indent);
            start = pos + 1;
        }

        return out;
    }
};
