#include <util/datetime/base.h>
#include <util/system/atexit.h>
#include <util/system/env.h>

#include "http_client.h"
#include "monitor_client.h"

TString TSolomonAgentBackend::PrepareData(const TSensorSettings &global, const TSensor &sensor) const {
    TString output;
    TStringOutput s(output);

    s << "sensor=" << sensor.Data.SensorName;
    s << "&value=" << sensor.Data.Value;

    if (!sensor.ServiceName.empty() && sensor.ServiceName != "default") {
        s << "&service=" << sensor.ServiceName;
    } else if (!global.ServiceName.empty() && global.ServiceName != "default") {
        s << "&service=" << global.ServiceName;
    }

    if (global.ModeDerived || sensor.ModeDerived) {
        s << "&mode=deriv";
    }

    if (sensor.Ttl > 0) {
        s << "&expire=" << sensor.Ttl;
    } else if (global.Ttl > 0) {
        s << "&expire=" << global.Ttl;
    }

    for (const auto &label : sensor.Labels) {
        s << "&label." << label.first << "=" << label.second;
    }

    return output;
}

TString TSolomonAgentBackend::DebugRequest(const TSensorSettings &global, const TVector<TSensor> &sensors) const {
    TString output;
    TStringOutput s(output);

    for (const TSensor &sensor : sensors) {
        s << Handler << "?" << PrepareData(global, sensor) << Endl;
    }

    return output;
}

void TSolomonAgentBackend::UpdateData(const TSensorSettings &global, const TVector<TSensor> &sensors) const {
    for (const TSensor &sensor : sensors) {
        try {
            NWebmaster::HttpGet(Handler + "?" + PrepareData(global, sensor), 500);
        } catch(...) {
        }
    }
}

TSolomonPushBackend::TSolomonPushBackend(const TString &project, const TString &cluster, const TString &host, const TString &handler)
    : Project(project)
    , Cluster(cluster)
    , Host(host)
    , Handler(handler)
{
}

void TSolomonPushBackend::WriteSensors(NJson::TJsonWriter &writer, const TSensorSettings &global, const TVector<TSensor> &sensors) const {
    writer.Write("sensors");
    writer.OpenArray();

    for (const TSensor &sensor : sensors) {
        writer.OpenMap();

        writer.Write("labels");
        writer.OpenMap();
        writer.Write("sensor", sensor.Data.SensorName);
        for (const auto &label : sensor.Labels) {
            writer.Write(label.first, label.second);
        }
        writer.CloseMap();

        if (global.Timestamp.Seconds() != 0) {
            writer.Write("ts", global.Timestamp.Seconds());
        } else {
            writer.Write("ts", sensor.Timestamp.Seconds());
        }
        writer.Write("value", sensor.Data.Value);

        if (global.ModeDerived || sensor.ModeDerived) {
            writer.Write("mode", "deriv");
        }

        writer.CloseMap();
    }

    writer.CloseArray();
}

void TSolomonPushBackend::WriteCommons(NJson::TJsonWriter &writer, const TSensorSettings &global) const {
    writer.Write("commonLabels");
    writer.OpenMap();

    if (Host.empty()) {
        ythrow yexception() << "Host mustn't be empty";
    }

    if (Project.empty()) {
        ythrow yexception() << "Project mustn't be empty";
    }

    if (Cluster.empty()) {
        ythrow yexception() << "Cluster mustn't be empty";
    }

    if (global.ServiceName.empty()) {
        ythrow yexception() << "global.ServiceName mustn't be empty";
    }

    writer.Write("host", Host);
    writer.Write("project", Project);
    writer.Write("cluster", Cluster);
    writer.Write("service", global.ServiceName);

    writer.CloseMap();
}

TString TSolomonPushBackend::PrepareData(const TSensorSettings &global, const TVector<TSensor> &sensors) const {
    TString json;
    TStringOutput jsonStream(json);
    NJson::TJsonWriter writer(&jsonStream, false, false);

    writer.OpenMap();
    WriteCommons(writer, global);
    WriteSensors(writer, global, sensors);
    writer.CloseMap();

    writer.Flush();
    return json;
}

TString TSolomonPushBackend::DebugRequest(const TSensorSettings &global, const TVector<TSensor> &sensors) const {
    return PrepareData(global, sensors);
}

const TString TSolomonPushBackend::GetToken() {
    static const TString token = GetEnv("SOLOMON_TOKEN", "XXX");
    return token;
}

void TSolomonPushBackend::UpdateData(const TSensorSettings &global, const TVector<TSensor> &sensors) const {
    TString json = PrepareData(global, sensors);
    try {
        static const TVector<TString> headers = {
            TString("Authorization: OAuth ") + GetToken()
        };

        const TString url = Handler
            + "?project=" + Project
            + "&service=" + global.ServiceName
            + "&cluster=" + Cluster
        ;

        NWebmaster::TPostRequest<TString> post(url);
        post.SetTimeout(500);
        post.SetContentType("application/json");
        post.SetData(json);
        post.SetHeaders(headers);
        TAtomicSharedPtr<TString> res = post.Perform();
        Y_UNUSED(*res);
    } catch(...) {
    }
}

TAtomic TMonitorBase::Instances = 0;

TMonitorBase::TMonitorBase(int period)
    : Active(true)
    , Period(period)
{
    AtExit(AtExitHandler, this);
    ThreadRef = SystemThreadFactory()->Run(this);
    AtomicIncrement(Instances);
}

TMonitorBase::~TMonitorBase() {
    ThreadRef->Join();
    AtomicDecrement(Instances);
}

void TMonitorBase::DoExecute() {
    while(Active) {
        for (int i = 0; i < Period && Active; i++) {
            Sleep(TDuration::Seconds(1));
        }
        Upload();
    }
}

void TMonitorBase::Upload() {
}

void TMonitorBase::Shutdown() {
    Active = false;
    ThreadRef->Join();
}

void TMonitorBase::AtExitHandler(void *ptr) {
    TMonitorBase *_this = static_cast<TMonitorBase*>(ptr);

    if (Instances > 0 && _this != nullptr) {
        _this->Shutdown();
    }
}
