#include "signals.h"

#include <saas/library/searchmap/searchmap.h>
#include <saas/searchproxy/common/abstract.h>
#include <search/session/reqenv.h>

#include <library/cpp/logger/global/global.h>
#include <library/cpp/http/misc/httpreqdata.h>

#include <library/cpp/cgiparam/cgiparam.h>

/////initiation/////

TSaasSearchProxySignals::TSaasSearchProxySignals() {
    AddCode(200);
    AddService("nservices");
}

void TSaasSearchProxySignals::HttpCodesToSignals(const THttpStatusManagerConfig& conf) {
    AddCode(conf.IncompleteStatus);
    AddCode(conf.SyntaxErrorStatus);
    AddCode(conf.EmptyRequestStatus);
    AddCode(conf.EmptySetStatus);
    AddCode(conf.UnknownErrorStatus);
    AddCode(conf.TimeoutStatus);
    AddCode(conf.PartialStatus);
}

void TSaasSearchProxySignals::BuildSignals(const TSearchProxyConfig& config){
    TSaasSearchProxySignals signals;
    DEBUG_LOG << "Init signals..." << Endl;
    auto addServiceSources = [&](const auto& service, const auto& serviceConfig) {
        if (auto* metasearchConfig = serviceConfig.GetMetaSearchConfig()) {
            for (const auto& source : metasearchConfig->SearchSources) {
                signals.ServiceSubsources[service].emplace(source.ProtoConfig_.GetServerDescr());
            }

            for (const auto& source : metasearchConfig->AuxSources) {
                signals.ServiceSubsources[service].emplace(source.ProtoConfig_.GetServerDescr());
            }
        }
    };

    const NSearchMapParser::TSearchMap& searchMap = config.GetSearchMap();
    for (auto& i : searchMap.GetServiceMap()) {
        signals.AddService(i.first);
        const auto& conf = config.GetServiceConfig(i.first).GetHttpStatusManagerConfig();
        signals.HttpCodesToSignals(conf);
        signals.RearrangeSignals.CreateServiceSignals(i.first, config.GetServiceConfig(i.first).GetCustomRearranges());

        auto& serviceConfig = config.GetServiceConfig(i.first);
        addServiceSources(i.first, serviceConfig);
    }
    for (auto& i : searchMap.GetMetaServices()) {
        signals.AddService(i.Name);
        const auto& conf = config.GetServiceConfig(i.Name).GetHttpStatusManagerConfig();
        signals.HttpCodesToSignals(conf);
        signals.RearrangeSignals.CreateServiceSignals(i.Name, config.GetServiceConfig(i.Name).GetCustomRearranges());

        for (auto& service: i.Components) {
            auto& serviceConfig = config.GetServiceConfig(service.ServiceName);
            addServiceSources(i.Name, serviceConfig);
        }
    }

    signals.Init(TUnistat::Instance());
    DEBUG_LOG << "Init signals... ok" << Endl;
}

/////signals drilling/////

void TSaasSearchProxySignals::InitServiceSignals(TUnistat& t, TString service, const TVector<double>& intervals) const {
    bool isService = (service != "");
    TString SERV = isService ? ("-" + service) : "";
    t.DrillFloatHole("search-CTYPE" + SERV + "-timeouted", "dmmm", Prio("", isService));
    t.DrillFloatHole("search-CTYPE" + SERV + "-not-fetched", "dmmm", Prio("", isService));
    t.DrillFloatHole("search-CTYPE" + SERV + "-incomplete", "dmmm", Prio("", isService));
    t.DrillFloatHole("search-CTYPE" + SERV + "-failed-attempts", "dmmm", Prio("", isService));
    t.DrillFloatHole("search-CTYPE" + SERV + "-count-zero", "dmmm", Prio("", isService));
    t.DrillFloatHole("search-CTYPE" + SERV + "-nonzero", "dmmm", Prio("", isService));

    t.DrillHistogramHole("times-CTYPE" + SERV, "dhhh", Prio("", isService), intervals);
    t.DrillHistogramHole("times-CTYPE-nonzero" + SERV, "dhhh", Prio("", isService), intervals);
    t.DrillHistogramHole("times-CTYPE-chno" + SERV, "dhhh", Prio("", isService, true), intervals);
    t.DrillHistogramHole("times-CTYPE-cache" + SERV, "dhhh", Prio("", isService, true), intervals);
    t.DrillHistogramHole("times-CTYPE-wait_in_queue" + SERV, "dhhh", Prio("", isService, true), intervals);
    t.DrillHistogramHole("times-CTYPE-report" + SERV, "dhhh", Prio("", isService, true), intervals);

    t.DrillFloatHole("search-CTYPE" + SERV + "-exceptions", "dmmm", Prio("", isService));
    t.DrillFloatHole("search-CTYPE" + SERV + "-unavailable", "dmmm", Prio("", isService));

    t.DrillFloatHole("search-CTYPE" + SERV + "-reqans_logging-inconsistency", "dmmm", Prio("", isService));
    t.DrillFloatHole("search-CTYPE" + SERV + "-reqans_logging-error", "dmmm", Prio("", isService));

    for (const TString& event : {"exception", "no_ticket", "bad_ticket", "access_denied", "ok"}) {
        for (const bool dryrun : {true, false}) {
            t.DrillFloatHole("search-CTYPE" + SERV + "-tvm-" + event + (dryrun ? "-dryrun" : ""), "dmmm", Prio("", isService));
        }
    }

    TSet<TString> metaSearchTypes;
    Singleton<IProxy::TFactory>()->GetKeys(metaSearchTypes);
    for (auto&& i : metaSearchTypes) {
        t.DrillFloatHole("search-CTYPE" + SERV + "-count-search-" + i, "dmmm", Prio("", isService));
    }

    RearrangeSignals.DrillServiceSignals(service, t);

    if (const THashSet<TString>* subsources = ServiceSubsources.FindPtr(service)) {
        for (auto& subsource: *subsources) {
            t.DrillFloatHole("search-CTYPE" + SERV + "-init_subsource_" + subsource, "dmmm", Prio("", isService));
            t.DrillFloatHole("search-CTYPE" + SERV + "-error_subsource_request_" + subsource, "dmmm", Prio("", isService));
            t.DrillFloatHole("search-CTYPE" + SERV + "-ok_subsource_" + subsource, "dmmm", Prio("", isService));
            t.DrillFloatHole("search-CTYPE" + SERV + "-ok_subsource-response_size_KB-" + subsource, "dmmm", Prio("", isService));

            t.DrillFloatHole("search-CTYPE" + SERV + "-all_subsource_request_" + subsource, "dmmm", Prio("", isService));
            t.DrillFloatHole("search-CTYPE" + SERV + "-all_subsource_request-body_size_KB-" + subsource, "dmmm", Prio("", isService));
            t.DrillFloatHole("search-CTYPE" + SERV + "-all_subsource_request-url_size_KB-" + subsource, "dmmm", Prio("", isService));

            t.DrillHistogramHole("times-CTYPE" + SERV + "-" + subsource + "_subsource", "dhhh", Prio("", isService), intervals);

            // from upper search
            t.DrillFloatHole("search-CTYPE" + SERV + "-rerequests_subsource_" + subsource, "dmmm", Prio("", isService));
            t.DrillFloatHole("search-CTYPE" + SERV + "-unanswers_subsource_" + subsource, "dmmm", Prio("", isService));
        }
    }
}

void TSaasSearchProxySignals::Init(TUnistat& t) const {
    static const TVector<double> intervals = {0, 1, 2, 3, 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> extendedIntervals = { 0, 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 40, 50,
                                        70, 90, 125, 175, 225, 300, 400, 600, 800,
                                        1000, 1500, 2000, 3000, 5000, 10000, 20000, 40000, 80000,
                                        120000, 200000, 400000, 600000, 800000, 1000000, 2000000};
    for (auto code : Codes) {
        t.DrillFloatHole("search-" + code, "dmmm", Prio(code));
        t.DrillFloatHole("search-CTYPE-" + code, "dmmm", Prio(code), NUnistat::TStartValue(0), EAggregationType::Sum, true);
        t.DrillFloatHole("search-CTYPE-chno-" + code, "dmmm", Prio(code, false, true));
        t.DrillFloatHole("search-CTYPE-cache-" + code, "dmmm", Prio(code, false, true));
    }

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

    InitServiceSignals(t, "", intervals);

    for (auto service : Services) {
        for (auto code : Codes) {
            t.DrillFloatHole("search-CTYPE-" + service + "-" + code, "dmmm", Prio(code, true), NUnistat::TStartValue(0), EAggregationType::Sum, true);
            t.DrillFloatHole("search-CTYPE-chno-" + service + "-" + code, "dmmm", Prio(code, true, true));
            t.DrillFloatHole("search-CTYPE-cache-" + service + "-" + code, "dmmm", Prio(code, true, true));
        }
        t.DrillHistogramHole("search-CTYPE-report-doc-count-" + service, "dhhh", Prio("", false, false), extendedIntervals);
        t.DrillHistogramHole("search-CTYPE-total-doc-count-" + service, "dhhh", Prio("", false, false), extendedIntervals);
        t.DrillHistogramHole("search-CTYPE-report-kilobyte-size-" + service, "dhhh", Prio("", false, false), extendedIntervals);
        InitServiceSignals(t, service, intervals);
    }

    static const TVector<double> expIntervals = {
        0, 0.1, 0.2, 0.3, 0.45, 0.67, 1, 1.5, 2.5,
        3.5, 5, 7.5, 11, 17, 26, 38,
        58, 86, 130, 190, 290, 440, 660,
        990, 1500, 2200, 3300, 5000, 7500, 11000
    };
    t.DrillHistogramHole("times-CTYPE-wait_in_common_queue", "dhhh", Prio("", false, true), expIntervals);
    t.DrillHistogramHole("search-CTYPE-active_objects", "dhhh", Prio("", false, true), expIntervals);
}

/////pushing/////

void TSaasSearchProxySignals::PushCodeSignal(const TString& code, const TString& service, const TString& cache) {
    TUnistat& inst = TUnistat::Instance();
    PushSignal(inst, "search-CTYPE-" + service + "-" + code, 1);
    PushSignal(inst, "search-CTYPE-" + code, 1);
    PushSignal(inst, "search-" + code, 1);
    PushSignal(inst, "search-CTYPE-" + cache + "-" + code, 1);
    PushSignal(inst, "search-CTYPE-" + cache + "-" + service + "-" + code, 1);
}

void TSaasSearchProxySignals::PushCodeSignals(const int code, const TString& service, const TString& cache) {
    const TString codeStr(ToString(code));
    const TString xxCode = codeStr.substr(0, 1) + "xx";
    TSaasSearchProxySignals::PushCodeSignal(xxCode, service, cache);
    TSaasSearchProxySignals::PushCodeSignal(codeStr, service, cache);
}

template <class TContext>
bool DoUnistatRecordImpl(const TContext& searchContext) {
    int code = searchContext.HttpStatus();
    const TSearchRequestData& rd = searchContext.GetRequestData();
    const TCgiParameters& cgi = rd.CgiParam;
    TString service = cgi.Get(NSearchProxyCgi::service);
    const TReqEnv* reqEnv = searchContext.GetReqEnv();

    if (service.find(',') != NPOS)
        service = "nservices";
    const TString cache = (reqEnv && (reqEnv->CacheHit() > 0.5)) ? "cache" : "chno";
    TUnistat& inst = TUnistat::Instance();
    TSaasSearchProxySignals::PushCodeSignals(code, service, cache);

    CHECK_WITH_LOG(Singleton<IProxy::TFactory>()->Has(searchContext.MetaSearchType()));

    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-count-search-" + searchContext.MetaSearchType(), 1);
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-count-search-" + searchContext.MetaSearchType(), 1);

    if (searchContext.Timeouted()) {
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-timeouted", 1);
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-timeouted", 1);
    }
    if (searchContext.NotFetched()) {
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-not-fetched", 1);
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-not-fetched", 1);
    }
    if (!searchContext.AnswerIsComplete()) {
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-incomplete", 1);
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-incomplete", 1);
    }
    if (searchContext.GetFailedAttempts()) {
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-failed-attempts", 1);
        TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-failed-attempts", 1);
    }

    ui64 dur = GetRequestDuration(rd) / 1000;
    double waitInQueueDuration = GetWaitInQueueDuration(searchContext) / 1000.0;
    double reportDuration = GetReportDuration(searchContext) / 1000.0;
    TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-wait_in_queue", waitInQueueDuration);
    TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-wait_in_queue-" + service, waitInQueueDuration);
    TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-report", reportDuration);
    TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-report-" + service, reportDuration);
    if ((code / 100) != 5) {
        TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE", dur);
        TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-" + service, dur);
    }
    if (code == 200) {
        bool nonzero = (searchContext.GetTotalDocsCount() > 0);
        if (nonzero) {
            TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-nonzero", 1);
            TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-nonzero", 1);
            TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-nonzero", dur);
            TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-nonzero-" + service, dur);
            TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-" + cache + "-" + service, dur);
            TSaasProxyUnistatSignals::PushSignal(inst, "times-CTYPE-" + cache, dur);
        } else {
            TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-count-zero", 1);
            TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-count-zero", 1);
        }
    }
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-report-doc-count-" + service, searchContext.GetReportDocsCount());
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-total-doc-count-" + service, searchContext.GetTotalDocsCount());
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-report-kilobyte-size-" + service, searchContext.GetReportByteSize() / 1024);
    return true;
}

bool TSaasSearchProxySignals::DoUnistatRecord(const TExtendedSearchContext& searchContext) {
    return ::DoUnistatRecordImpl<TExtendedSearchContext>(searchContext);
}

bool TSaasSearchProxySignals::DoUnistatRecord(const TExtendedReplyContext& searchContext) {
    bool result = ::DoUnistatRecordImpl<TExtendedReplyContext>(searchContext);
    auto& unistat = TUnistat::Instance();
    double commonQueueDuration = searchContext.GetCommonQueueDuration().MicroSeconds() / 1000.0;
    TSaasProxyUnistatSignals::PushSignal(unistat, "times-CTYPE-wait_in_common_queue", commonQueueDuration);
    TSaasProxyUnistatSignals::PushSignal(unistat, "search-CTYPE-active_objects", searchContext.GetActiveObjects());
    return result;
}

bool TSaasSearchProxySignals::DoUnistatReqansLoggingError(const TString& service) {
    TUnistat& inst = TUnistat::Instance();
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-reqans_logging-error", 1);
    return true;
}

bool TSaasSearchProxySignals::DoUnistatReqansLoggingIncosistency(const TString& service) {
    TUnistat& inst = TUnistat::Instance();
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-reqans_logging-inconsistency", 1);
    return true;
}

bool TSaasSearchProxySignals::DoUnistatExceptionRecord(const TString& service) {
    TUnistat& inst = TUnistat::Instance();
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-exceptions", 1);
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-exceptions", 1);
    return true;
}

bool TSaasSearchProxySignals::DoUnistatUnavailable(const TString& service) {
    PushGlobalSignal({ "search-CTYPE-unavailable", "search-CTYPE-" + service + "-unavailable" }, 1);
    return true;
}

void TSaasSearchProxySignals::PushTvmCodeSignals(const TString& service, int code) {
    static const TString cache = "chno";
    PushCodeSignals(code, service, cache);
}

bool TSaasSearchProxySignals::DoUnistatTvmException(const TString& service, const bool dryrun) {
    return DoUnistatTvm(service, dryrun, "exception");
}

bool TSaasSearchProxySignals::DoUnistatTvmNoTicket(const TString& service, const bool dryrun) {
    if (!dryrun) {
        PushTvmCodeSignals(service, HTTP_UNAUTHORIZED);
    }
    return DoUnistatTvm(service, dryrun, "no_ticket");
}

bool TSaasSearchProxySignals::DoUnistatTvmBadTicket(const TString& service, const bool dryrun) {
    if (!dryrun) {
        PushTvmCodeSignals(service, HTTP_UNAUTHORIZED);
    }
    return DoUnistatTvm(service, dryrun, "bad_ticket");
}

bool TSaasSearchProxySignals::DoUnistatTvmAccessDenied(const TString& service, const bool dryrun) {
    if (!dryrun) {
        PushTvmCodeSignals(service, HTTP_FORBIDDEN);
    }
    return DoUnistatTvm(service, dryrun, "access_denied");
}

bool TSaasSearchProxySignals::DoUnistatTvmAccessGranted(const TString& service, const bool dryrun) {
    return DoUnistatTvm(service, dryrun, "ok");
}

bool TSaasSearchProxySignals::DoUnistatTvm(const TString& service, const bool dryrun, const TString& event) {
    TUnistat& inst = TUnistat::Instance();
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-tvm-" + event + (dryrun ? "-dryrun" : ""), 1);
    TSaasProxyUnistatSignals::PushSignal(inst, "search-CTYPE-" + service + "-tvm-" + event + (dryrun ? "-dryrun" : ""), 1);
    return true;
}
