#include "server.h"

#include <saas/util/queue.h>

#include <library/cpp/json/json_writer.h>
#include <library/cpp/yconf/conf.h>
#include <util/random/random.h>
#include <saas/util/network/http_request.h>
#include <search/request/data/reqdata.h>
#include <util/string/split.h>

TChartBuilderConfig::TChartBuilderConfig(const TServerConfigConstructorParams& params)
    : DaemonConfig(*params.Daemon)
{
    TAnyYandexConfig config;
    if(!config.ParseMemory(params.Text.data())) {
        TString err;
        config.PrintErrors(err);
        FAIL_LOG("%s", err.data());
    }

    TYandexConfig::Section* emSection = config.GetFirstChild("Server");
    TYandexConfig::TSectionsMap children = emSection->GetAllChildren();

    TYandexConfig::TSectionsMap::const_iterator i = children.find("HttpOptions");
    VERIFY_WITH_LOG(i != children.end(), "cannot find HttpOptions section");
    ServerOptions = TDaemonConfig::ParseHttpServerOptions(i->second->GetDirectives());

    i = children.find("Intervals");
    if (i != children.end()) {
        TYandexConfig::TSectionsMap intervals = i->second->GetAllChildren();
        for (auto range = intervals.equal_range("Interval"); range.first != range.second; ++range.first) {
            const TYandexConfig::Directives& dir = range.first->second->GetDirectives();
            Intervals.emplace_back(dir.Value<i32>("From", -Max<i32>()), dir.Value<i32>("To", Max<i32>()), dir.Value("Names", Default<TString>()));
        }
    }

    auto& directives = emSection->GetDirectives();

    directives.GetValue("ReaskHost", ReaskHost);
    directives.GetValue("ReaskPort", ReaskPort);
    TString ctypes = directives.Value<TString>("CTypes", "stable_maps");
    CTypes = StringSplitter(ctypes).SplitBySet(" ,").SkipEmpty();
};

struct TLabels {
    TString GetName() const {
        TString result;
        for (const auto& label : Labels) {
            result += "&" + label.first + "=" + label.second;
        }
        return result;
    }

    TMap<TString, TString> Labels;
};

struct TIntervalData {
    ui64 Count = 0;
    double Percent = 0.;
};

struct THostData {
    TVector<TIntervalData> DataByInterval;
    ui64 MCount = 0;
};

class IAccumulator {
public:
    virtual ~IAccumulator() {}
    virtual void AddData(const THostData& data, const TLabels& labels) = 0;
    virtual void SaveSensors(NJson::TJsonValue& sensors, const TLabels& addLabels) const = 0;
    virtual const TChartBuilderConfig& GetConfig() const = 0;
};

class TAccumulator : public TSimpleRefCount<TAccumulator>, public IAccumulator {
public:
    typedef TIntrusivePtr<TAccumulator> TPtr;

public:
    TAccumulator(IAccumulator& owner, const TLabels& labels)
        : Owner(owner)
        , Labels(labels)
    {}

    const TChartBuilderConfig& GetConfig() const override {
        return Owner.GetConfig();
    }

    void AddData(const THostData& data, const TLabels& labels) override {
        if (!HostsCount) {
            MaxH = data;
            MinH = data;
            Sum = data;
        } else {
            MaxH.MCount = Max(MaxH.MCount, data.MCount);
            MinH.MCount = Min(MinH.MCount, data.MCount);
            Sum.MCount += data.MCount;
            for (ui32 i = 0; i < data.DataByInterval.size(); ++i) {
                MaxH.DataByInterval[i].Percent = Max(MaxH.DataByInterval[i].Percent, data.DataByInterval[i].Percent);
                MinH.DataByInterval[i].Percent = Min(MinH.DataByInterval[i].Percent, data.DataByInterval[i].Percent);
                Sum.DataByInterval[i].Count += data.DataByInterval[i].Count;
                if (Sum.MCount) {
                    Sum.DataByInterval[i].Percent = 1000 * Sum.DataByInterval[i].Count / Sum.MCount * .1;
                }
            }
        }
        ++HostsCount;
        for (const auto& label : labels.Labels) {
            TLabels copy = labels;
            copy.Labels.erase(label.first);
            Owner.AddData(data, copy);
        }
    }


    void SaveSensors(NJson::TJsonValue& sensors, const TLabels& addLabels) const override {
        if (!HostsCount)
            return;
        SaveSensor(sensors, Sum, "average", addLabels);
        SaveSensor(sensors, MinH, "min", addLabels);
        SaveSensor(sensors, MaxH, "max", addLabels);
    }

private:
    void SaveSensor(NJson::TJsonValue& sensors, const THostData& data, const TString& func, const TLabels& addLabels) const {
        for (ui32 interval = 0; interval < data.DataByInterval.size(); ++interval) {
            for (const TString& name: GetConfig().Intervals[interval].GetNames()) {
                FillLables(sensors.AppendValue(NJson::JSON_MAP), func, name, data.DataByInterval[interval].Percent, addLabels);
            }
        }
        FillLables(sensors.AppendValue(NJson::JSON_MAP), func, "MCount", data.MCount, addLabels);
    }

    void FillLables(NJson::TJsonValue& sensor, const TString& func, const TString& name, const NJson::TJsonValue& value, const TLabels& addLabels) const {
        NJson::TJsonValue& labels = sensor["labels"];
        labels["sensor"] = name;
        labels["func"] = func;
        for (const auto& label : Labels.Labels)
            labels[label.first] = label.second;
        for (const auto& label : addLabels.Labels)
            labels[label.first] = label.second;
        sensor["value"] = value;
    }

private:
    IAccumulator& Owner;
    TLabels Labels;
    THostData MaxH;
    THostData MinH;
    THostData Sum;
    ui64 HostsCount = 0;
};

class TAccumulatorsHolder : public IAccumulator {
public:
    TAccumulatorsHolder(const TChartBuilderConfig& config)
        : Config(config)
    {}

    void AddData(const THostData& data, const TLabels& labels) override {
        TAccumulator::TPtr& acc = Accomulators[labels.GetName()];
        if (!acc)
            acc = MakeIntrusive<TAccumulator>(*this, labels);
        acc->AddData(data, labels);
    }

    void SaveSensors(NJson::TJsonValue& sensors, const TLabels& addLabels) const override {
        for (const auto& acc: Accomulators)
            acc.second->SaveSensors(sensors, addLabels);
    }

    const TChartBuilderConfig& GetConfig() const override {
        return Config;
    }

private:
    const TChartBuilderConfig& Config;
    THashMap<TString, TAccumulator::TPtr> Accomulators;
};

class TChartRequestReplier: public THttpClientRequestExtImpl<TSearchRequestData> {
private:
    const TChartBuilderConfig& Config;

    bool FillSensors(NJson::TJsonValue& sensors, const NJson::TJsonValue& receive, const TString& ctype) {
        TAccumulatorsHolder accomulatorsRty(Config);
        TAccumulatorsHolder accomulatorsFull(Config);
        TLabels commonLables;
        commonLables.Labels["ctype"] = ctype;
        for (const auto& service : receive.GetMap()) {
            for (const auto& slot : service.second.GetMap()) {
                const NJson::TJsonValue& receiveDistr = slot.second["result"]["distribution_receive"];
                const NJson::TJsonValue& receiveOwn = slot.second["result"]["distribution_owned"];

                if (!receiveDistr.Has("SUM") || !receiveDistr["SUM"].IsUInteger()) {
                    ERROR_LOG << slot.first << ": incorrect reply format: " << receiveDistr.GetStringRobust() << Endl;
                    continue;
                }

                if (!receiveOwn.Has("SUM") || !receiveOwn["SUM"].IsUInteger()) {
                    ERROR_LOG << slot.first << ": incorrect reply format: " << receiveOwn.GetStringRobust() << Endl;
                    continue;
                }

                if (!receiveDistr.IsMap() || !receiveOwn.IsMap()) {
                    ERROR_LOG << slot.first << ": incorrect reply format: " << slot.second.GetStringRobust() << Endl;
                    continue;
                }
                TLabels labels;
                labels.Labels["datacenter"] = slot.second["$datacenter_alias$"].GetStringRobust();
                labels.Labels["saas_service"] = service.first;
                if (!FillSensors(receiveDistr, accomulatorsRty, labels, receiveDistr["SUM"].GetUInteger()))
                    continue;
                if (!FillSensors(receiveOwn, accomulatorsFull, labels, receiveOwn["SUM"].GetUInteger()))
                    continue;
            }
        }
        commonLables.Labels["chain"] = "full";
        accomulatorsFull.SaveSensors(sensors, commonLables);
        commonLables.Labels["chain"] = "rty";
        accomulatorsRty.SaveSensors(sensors, commonLables);
        return true;
    }

    bool FillSensors(const NJson::TJsonValue& receive, TAccumulatorsHolder& accomulators, const TLabels& commonLabels, ui64 messCount) {
        THostData hd;
        hd.DataByInterval.resize(Config.Intervals.size());
        hd.MCount = messCount;
        for (auto&& i : receive.GetMap()) {
            TString strInfo = i.second.GetStringRobust();
            TVector<TString> v = StringSplitter(strInfo).SplitBySet("=();").SkipEmpty();
            if (i.first == "SUM") {
                continue;
            }
            if (v.size() < 6) {
                ERROR_LOG << "incorrect reply format: " << i.second.GetStringRobust() << " / " << v.size() << Endl;
                return false;
            }
            double perc = FromString<double>(v[3]);
            ui64 count = FromString<ui64>(v[1]);
            TVector<TString> intervalV = StringSplitter(i.first).SplitBySet("[-,)").SkipEmpty();
            if (intervalV.size() < 2) {
                ERROR_LOG << "incorrect reply format: " << i.first << " / " << intervalV.size() << Endl;
                return false;
            }
            const double from = FromStringWithDefault<double>(intervalV[0], -Max<i32>());
            const double to = FromStringWithDefault<double>(intervalV[1], Max<i32>());
            TInterval<i32> receivedInterval(from, to);
            for (ui32 interval = 0; interval < Config.Intervals.size(); ++interval) {
                if (receivedInterval.IsContainedIn(Config.Intervals[interval])) {
                    hd.DataByInterval[interval].Percent += perc;
                    hd.DataByInterval[interval].Count += count;
                }
            }
        }
        accomulators.AddData(hd, commonLabels);
        return true;
    }

public:

    TChartRequestReplier(const TChartBuilderConfig& config)
        : Config(config)
    {

    }

    bool Reply(void* /*ThreadSpecificResource*/) override {
        DEBUG_LOG << "action=parsing_client_data;status=start" << Endl;
        ProcessHeaders();
        RD.Scan();
        DEBUG_LOG << RD.CgiParam.Print() << Endl;
        NJson::TJsonValue result;
        for (const auto& ctype : Config.CTypes) {
            NUtil::THttpRequest request("/broadcast?command=reduce_control&action=get_info&ctype=" + ctype);
            request.SetTimeout(2000);
            NUtil::THttpReply reply = request.Execute(Config.ReaskHost, Config.ReaskPort);
            NJson::TJsonValue replyJson;
            try {
                if (reply.IsSuccessReply()) {
                    if (!NJson::ReadJsonFastTree(reply.Content(), &replyJson)) {
                        ERROR_LOG << ctype << ": incorrect reply format" << reply.Content() << Endl;
                        continue;
                    }
                    FillSensors(result["sensors"], replyJson, ctype);
                } else {
                    ERROR_LOG << ctype << ": Can't connect (" << reply.Code() << ", " << reply.ErrorMessage() << ")" << Endl;
                }
            } catch (...) {
                ERROR_LOG << CurrentExceptionMessage() << Endl;
            }
        }
        Output() << "HTTP/1.1 200 Ok\r\nContent-Type:text/plain\r\n\r\n";
        Output() << result.GetStringRobust() << Endl;
        return true;
    }
};

TClientRequest* THttpChartServer::CreateClient() {
    return new TChartRequestReplier(Config);
}

TChartBuilderConfig::TInterval::TInterval(const i32 min, const i32 max, const TString& name)
    : ::TInterval<i32>(min, max)
{
    SetNames(name);
}

void TChartBuilderConfig::TInterval::SetNames(const TString& value) {
    Names.clear();
    if (value)
        Names = SplitString(value, ",");
    else if (!HasMin() && !HasMax())
        Names.emplace_back("all");
    else if (!HasMin())
        Names.emplace_back("< " + ::ToString(GetMax()) + "s");
    else if (!HasMax())
        Names.emplace_back("> " + ::ToString(GetMin()) + "s");
    else
        Names.emplace_back("[" + ToString() + ")s");
}
