#include "foundation/diagnostics/ExecutionPath.h"

#include <atomic>
#include <mutex>
#include <unordered_map>
#include <utility>

#include "foundation/diagnostics/ActionOutcome.h"
#include "foundation/diagnostics/BuildOptions.h"
#include "foundation/diagnostics/Logger.h"

namespace
{
    struct TraceMetadata final
    {
        std::string feature;
    };

    std::atomic<ta::foundation::execution_path::TraceId> g_next_trace_id{0};
    thread_local ta::foundation::execution_path::TraceId g_current_trace_id = 0;
    std::mutex g_trace_mutex;
    std::unordered_map<ta::foundation::execution_path::TraceId, TraceMetadata> g_trace_metadata;

    std::string finish_trace(const ta::foundation::execution_path::TraceId trace_id)
    {
        std::scoped_lock lock(g_trace_mutex);
        const auto found = g_trace_metadata.find(trace_id);
        if (found == g_trace_metadata.end())
            return {};
        std::string feature = std::move(found->second.feature);
        g_trace_metadata.erase(found);
        return feature;
    }
}

namespace ta::foundation::execution_path
{
    TraceId begin(std::string feature,
                  std::string action,
                  std::string detail)
    {
        const TraceId trace_id = g_next_trace_id.fetch_add(1, std::memory_order_relaxed) + 1;
        {
            std::scoped_lock lock(g_trace_mutex);
            g_trace_metadata.emplace(trace_id, TraceMetadata{feature});
        }

        std::string description = std::move(action);
        if (!detail.empty())
            description += " | " + detail;
        ta::foundation::logger::path(trace_id, std::move(feature), "begin", std::move(description));
        return trace_id;
    }

    TraceId current() noexcept
    {
        return g_current_trace_id;
    }

    TraceId current_or_begin(std::string feature,
                             std::string action,
                             std::string detail)
    {
        const TraceId trace_id = current();
        return trace_id != 0
            ? trace_id
            : begin(std::move(feature), std::move(action), std::move(detail));
    }

    void step(const TraceId trace_id,
              std::string component,
              std::string stage,
              std::string detail)
    {
        if (trace_id == 0 || !ta::foundation::diagnostics::build_options::verbose)
            return;
        ta::foundation::logger::path(trace_id, std::move(component), std::move(stage), std::move(detail));
    }

    void complete(const TraceId trace_id,
                  std::string component,
                  std::string detail)
    {
        if (trace_id == 0)
            return;

        std::string feature = finish_trace(trace_id);
        ta::foundation::logger::path(trace_id, component, "complete", detail);
        ta::foundation::action_outcomes::publish({
            trace_id,
            std::move(feature),
            std::move(component),
            ta::foundation::action_outcomes::Stage::Succeeded,
            std::move(detail)
        });
    }

    void fail(const TraceId trace_id,
              std::string component,
              std::string detail)
    {
        if (trace_id == 0)
            return;

        std::string feature = finish_trace(trace_id);
        ta::foundation::logger::path(trace_id, component, "failed", detail);
        ta::foundation::action_outcomes::publish({
            trace_id,
            std::move(feature),
            std::move(component),
            ta::foundation::action_outcomes::Stage::Failed,
            std::move(detail)
        });
    }

    ScopedContext::ScopedContext(const TraceId trace_id) noexcept
        : previous_(g_current_trace_id)
    {
        g_current_trace_id = trace_id;
    }

    ScopedContext::~ScopedContext() noexcept
    {
        g_current_trace_id = previous_;
    }
}
