#include "signals.h"
#include <saas/indexerproxy/configs/config.h>

#include <saas/library/behaviour/behaviour.h>
#include <library/cpp/http/misc/httpcodes.h>
#include <library/cpp/logger/global/global.h>
#include <util/string/cast.h>
#include <util/string/join.h>
#include <util/system/datetime.h>

TSet<TString> TSaasIndexerProxySignals::TrackedDistrAttrs;

TSaasIndexerProxySignals::TSaasIndexerProxySignals() {
    for (auto code : GetDispCodes()) {
        Codes.insert(ToString(code));
        Codes.insert(ToString(code).substr(0, 1) + "xx");
        Codes.insert("all");
    }
}


void TSaasIndexerProxySignals::AddDistrAttr(const TString &distrAttr) {
    DistrAttrs.insert(distrAttr);
}

void TSaasIndexerProxySignals::BuildSignals(const TSet<TString>& services, const TProxyServicesConfig& config) {
    TSaasIndexerProxySignals signals;
    for (const auto& i : services) {
        signals.AddService(i);
    }

    const auto& distrMetrics = config.GetDefaultConfig().DistributionMetrics;
    for (const TString& distrAttr : distrMetrics) {
        signals.AddDistrAttr(distrAttr);
    }

    signals.Init(TUnistat::Instance());
}


void TSaasIndexerProxySignals::DrillForSources(TUnistat& t, const TString& signalBeg, const TString& signalEnd,
    NUnistat::TPriority prio, const TVector<double>& intervals, bool visible) {
    if (intervals.size() > 0) {
        t.DrillHistogramHole(signalBeg + signalEnd, "dhhh", prio, intervals);
        t.DrillHistogramHole(signalBeg + "-default" + signalEnd, "dhhh", prio, intervals);
    }
    else {
        t.DrillFloatHole(signalBeg + signalEnd, "dmmm", prio, NUnistat::TStartValue(0), EAggregationType::Sum, visible);
        t.DrillFloatHole(signalBeg + "-default" + signalEnd, "dmmm", prio, NUnistat::TStartValue(0), EAggregationType::Sum, visible);
   }
}

void TSaasIndexerProxySignals::Init(TUnistat& t) const {
    static const TVector<double> intervals = { 0, 5, 10, 15, 20, 25, 30,
        40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, 225, 250, 300, 350, 400,
        500, 600, 700, 800, 900, 1000, 1500, 2000, 3000, 5000, 10000 };
    static const TVector<double> ages = { 0, 1, 2, 3, 4, 5, 7, 10, 15, 20, 25, 30, 45, 60, 90, 120, 180, 300, 420, 600,
        900, 1200, 1800, 2400, 3600, 5400, 7200, 9000, 10800, 3600*4, 3600*6, 3600*8, 3600*10, 3600*12, 3600*18, 3600*24,
        3600 * 36, 3600 * 48, 3600 * 72 };

    t.DrillFloatHole("debug-errors", "dmmm", Prio(""));
    t.DrillFloatHole("debug-errors-CTYPE", "dmmm", Prio(""));

    t.DrillFloatHole("index-total-CTYPE-all", "dmmm", Prio(""));

    for (auto code : Codes) {
        t.DrillFloatHole("index-" + code, "dmmm", Prio(code, false));

        DrillForSources(t, "index", "-CTYPE-" + code, Prio(code, false), TVector<double>(), true);
        DrillForSources(t, "index", "-wmeta-CTYPE-" + code, Prio(code, false, true));

        t.DrillFloatHole("index-defer-" + code, "dmmm", Prio(code, false, true));
        t.DrillFloatHole("index-defer-CTYPE-" + code, "dmmm", Prio(code, false));
    }
    DrillForSources(t, "index", "-CTYPE-duplicated", Prio("", false));

    DrillForSources(t, "times", "-CTYPE", Prio("", false), intervals);
    DrillForSources(t, "times", "-wmeta-CTYPE", Prio("", false, true), intervals);

    DrillForSources(t, "ages", "-CTYPE", Prio("", false), ages);
    DrillForSources(t, "ages", "-wmeta-CTYPE", Prio("", false, true), ages);

    for (const auto& distrAttr : DistrAttrs) {
        t.DrillFloatHole("distattr-" + distrAttr + "-CTYPE", "dmmm", NUnistat::TPriority(55), NUnistat::TStartValue(0), EAggregationType::Sum);
    }

    TrackedDistrAttrs = std::move(DistrAttrs); //save the list for usage at runtime

    for (auto service : Services) {
        for (auto code : Codes) {
            bool visible = (code == "200") || (code == "all") || (code.find("xx") != TString::npos);
            DrillForSources(t, "index", "-CTYPE-" + service + "-" + code, Prio(code, true), TVector<double>(), visible);

            t.DrillFloatHole("index-" + service + "-" + code, "dmmm", Prio(code, true, true));
            t.DrillFloatHole("index-defer-CTYPE-" + service + "-" + code, "dmmm", Prio(code, true, true));
        }
        DrillForSources(t, "index", "-CTYPE-" + service + "-duplicated", Prio("", true));

        t.DrillFloatHole("index-total-CTYPE-" + service + "-all", "dmmm", Prio("", true, true));
        DrillForSources(t, "times", "-CTYPE-" + service, Prio("", true), intervals);
        DrillForSources(t, "ages", "-CTYPE-" + service, Prio("", true), ages);
    }
}

bool TSaasIndexerProxySignals::DoUnistatRecord(const TUnistatRecordContext& rec) {
    TUnistat& inst = TUnistat::Instance();

    TString source = rec.IsDeferred ? "defer" : rec.Source;
    TString sourceCommon = (rec.IsFromMeta && !rec.IsDeferred) ? source + "-wmeta" : source;
    TString code = ToString(rec.Code);
    TString xxCode = code.substr(0, 1) + "xx";

    PushSignalWithCode(inst, "index-" + sourceCommon + "-CTYPE-", code, xxCode);
    PushSignalWithCode(inst, "index-" + source + "-CTYPE-" + rec.Service + "-", code, xxCode);
    PushSignal(inst, "index-" + sourceCommon + "-CTYPE-all", 1);
    PushSignal(inst, "index-" + source + "-CTYPE-" + rec.Service + "-all", 1);
    PushSignal(inst, "index-total-CTYPE-all", 1);
    PushSignal(inst, "index-total-CTYPE-" + rec.Service + "-all", 1);

    for (const auto& distAttr : rec.DistrAttrs) {
        if (TrackedDistrAttrs.find(distAttr) != TrackedDistrAttrs.end())
            PushSignal(inst, (TString)"distattr-" + distAttr + "-CTYPE", 1);
    }

    if (rec.IsDeferred)
        return true;

    PushSignalWithCode(inst, "index-CTYPE-", code, xxCode);
    PushSignalWithCode(inst, "index-", code, xxCode);
    PushSignalWithCode(inst, "index-CTYPE-" + rec.Service + "-", code, xxCode);
    PushSignal(inst, "index-CTYPE-all", 1);
    PushSignal(inst, "index-CTYPE-" + rec.Service + "-all", 1);

    if (rec.Code == 200) {
        PushSignal(inst, "times-CTYPE", rec.DurationMs);
        PushSignal(inst, "times-" + sourceCommon + "-CTYPE", rec.DurationMs);
        PushSignal(inst, "times-CTYPE-" + rec.Service, rec.DurationMs);
        PushSignal(inst, "times-" + source + "-CTYPE-" + rec.Service, rec.DurationMs);

        PushSignal(inst, "ages-CTYPE", rec.AgeSec);
        PushSignal(inst, "ages-" + sourceCommon + "-CTYPE", rec.AgeSec);
        PushSignal(inst, "ages-CTYPE-" + rec.Service, rec.AgeSec);
        PushSignal(inst, "ages-" + source + "-CTYPE-" + rec.Service, rec.AgeSec);
    }

    if (rec.IsDuplicated) {
        PushSignal(inst, "index-CTYPE-duplicated", 1);
        PushSignal(inst, "index-" + sourceCommon + "-CTYPE-duplicated", 1);
        PushSignal(inst, "index-CTYPE-" + rec.Service + "-duplicated", 1);
        PushSignal(inst, "index-" + source + "-CTYPE-" + rec.Service + "-duplicated", 1);
    }
    return true;
}

TPersqueueServiceSignals::TPersqueueServiceSignals(const TString& service, const TVector<TString>& shards)
    : Service(service)
    , Shards(shards)
{
    Init(TUnistat::Instance());
}

void TPersqueueServiceSignals::Processed(bool written, TStringBuf shard, ui64 process_time) {
    TUnistat& I = TUnistat::Instance();
    PushSignal(I, GetSignalSuffix(shard, "all"), 1);
    if (written) {
        PushSignal(I, GetSignalSuffix(shard, "ok"), 1);
        PushSignal(I, GetSignalSuffix(shard, "write_time"), process_time);
    }
    else {
        PushSignal(I, GetSignalSuffix(shard, "failed"), 1);
    }
}

void TPersqueueServiceSignals::UpdateInFlight(TStringBuf shard, TAtomicCounter& inFlight) {
    TUnistat& I = TUnistat::Instance();
    PushSignal(I, GetSignalSuffix(shard, "in_flight"), inFlight.Val());
}

void TPersqueueServiceSignals::Init(TUnistat& t) const {
    static const TVector<double> intervals = { 0, 100, 250, 500, 750,
        1000, 1500, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000,
        10000, 15000, 20000, 30000, 40000, 50000, 60000 };
    for (auto&& shard : Shards) {
        t.DrillFloatHole(GetSignalSuffix(shard, "all"), "dmmm", Prio("", false));
        t.DrillFloatHole(GetSignalSuffix(shard, "ok"), "dmmm", Prio("", false));
        t.DrillFloatHole(GetSignalSuffix(shard, "failed"), "dmmm", Prio("", false));
        t.DrillFloatHole(GetSignalSuffix(shard, "in_flight"), "ammx", Prio("", false), NUnistat::TStartValue(0), EAggregationType::LastValue);
        t.DrillHistogramHole(GetSignalSuffix(shard, "write_time"), "dhhh", Prio("", false), intervals);
    }
}

TString TPersqueueServiceSignals::GetSignalSuffix(TStringBuf shard, const TStringBuf name) const {
    return Join("-", "index-persqueue", Service, shard, name);
}
