#include <library/cpp/json/json_value.h>

#include <util/generic/map.h>
#include <util/string/cast.h>
#include <util/system/types.h>

namespace NRTYDeploy {
    struct MinMaxAggregator {
        void AddValue(ui32 value) {
            ++Count;
            if (value < MinValue) {
                MinValue = value;
            }
            if (value > MaxValue) {
                MaxValue = value;
            }
        }

        void ReportStatus(TStringBuf statusKey, NJson::TJsonValue& status) const {
            NJson::TJsonValue result{NJson::JSON_MAP};
            if (Count > 0) {
                result.InsertValue("min", MinValue);
                result.InsertValue("max", MaxValue);
            } else {
                result.InsertValue("min", 0);
                result.InsertValue("max", 0);
            }
            status.InsertValue(statusKey, std::move(result));
        }

    private:
        ui32 MinValue = Max<ui32>();
        ui32 MaxValue = 0;
        ui32 Count = 0;
    };

    struct ErrorsAggregator {
        ErrorsAggregator(ui32 capacity)
            : Capacity(capacity)
        {
        }

        void AddValue(const TString& error, const TString& strTimestamp) {
            ui64 timestamp = FromString<ui64>(strTimestamp);
            if (Errors.size() == Capacity && timestamp <= Errors.begin()->first) {
                return;
            }
            Errors.emplace(timestamp, error);
            while (Errors.size() > Capacity) {
                Errors.erase(Errors.begin());
            }
        }

        void ReportStatus(TStringBuf statusKey, NJson::TJsonValue& status) const {
            NJson::TJsonValue result{NJson::JSON_ARRAY};
            for (auto it = Errors.rbegin(), itEnd = Errors.rend(); it != itEnd; ++it) {
                NJson::TJsonValue row{NJson::JSON_MAP};
                row.InsertValue("error", it->second);
                row.InsertValue("time_us", ToString(it->first));
                result.AppendValue(std::move(row));
            }
            status.InsertValue(statusKey, std::move(result));
        }

    private:
        const ui32 Capacity = 1;
        TMultiMap<ui64, TString> Errors;
    };

    void AggregateMapReduceStatus(const NJson::TJsonValue& docfetcherStatus, NJson::TJsonValue& report, const TString& serviceKey) {
        const auto& serviceMap = docfetcherStatus.GetMapSafe();
        MinMaxAggregator timestampAggregator;
        ErrorsAggregator errorsAggregator{5};
        const auto& slotsMap = serviceMap.at(serviceKey).GetMapSafe();
        ui32 skipped = 0;
        for (auto& slot : slotsMap) {
            const auto& slotMap = slot.second.GetMapSafe();
            auto statusIt = slotMap.find("status");
            if (statusIt == slotMap.end()) {
                // skip dead replicas
                ++skipped;
                continue;
            }
            const auto& statusEntry = statusIt->second.GetMapSafe();
            if (statusEntry.contains("SearchableTimestamp")) {
                timestampAggregator.AddValue(statusEntry.at("SearchableTimestamp").GetUIntegerSafe());
            } else {
                timestampAggregator.AddValue(0);
            }
            if (statusEntry.contains("Errors")) {
                const auto& slotErrors = statusEntry.at("Errors").GetArraySafe();
                for (auto& jsonError : slotErrors) {
                    const auto& record = jsonError.GetMapSafe();
                    errorsAggregator.AddValue(record.at("error").GetStringSafe(), record.at("time_us").GetStringSafe());
                }
            }
        }
        NJson::TJsonValue stats;
        timestampAggregator.ReportStatus("SearchableTimestamp", stats);
        errorsAggregator.ReportStatus("Errors", stats);
        stats.InsertValue("SkippedInstances", skipped);
        report.InsertValue("stats", std::move(stats));
    }
};

