#pragma once

#include <stdexcept>
#include <string>

#include <library/cpp/json/json_writer.h>
#include <library/cpp/neh/neh.h>
#include <library/cpp/neh/location.h>

#include <util/generic/noncopyable.h>
#include <util/generic/ptr.h>
#include <util/generic/vector.h>
#include <util/stream/str.h>
#include <util/stream/output.h>
#include <library/cpp/deprecated/atomic/atomic.h>
#include <util/system/mutex.h>
#include <util/thread/factory.h>
#include <utility>

#include "fqdn.h"

struct TSensorSettings {
    inline TSensorSettings()
        : ModeDerived(false)
        , Ttl(-1)
    {
    }

    virtual ~TSensorSettings() { }

public:
    TString ServiceName;
    bool ModeDerived;
    int Ttl;
    TInstant Timestamp;
};

struct TSensorData {
    inline TSensorData(const TString &sensorName, double value = 0.)
        : SensorName(sensorName)
        , Value(value)
    {
    }

    inline TSensorData(const TSensorData &data)
        : SensorName(data.SensorName)
        , Value(data.Value)
    {
    }

public:
    void Reset(double n = 0.) {
        TGuard<TMutex> guard(Mutex);
        Value = n;
    }

    inline double operator()(double n = 1.) {
        TGuard<TMutex> guard(Mutex);
        Value += n;
        return Value;
    }

public:
    TString SensorName;
    double Value;
    TMutex Mutex;
};

struct TSensor : public TSensorSettings {
    typedef std::pair<TString, TString> TLabel;

    inline TSensor(const TString &sensorName, double value, TInstant ts = Now())
        : Data(sensorName, value)
        , Timestamp(ts)
    {
    }

    inline TSensor(const TSensorData &data, TInstant ts = Now())
        : Data(data)
        , Timestamp(ts)
    {
    }

public:
    TSensorData Data;
    TInstant Timestamp;
    TVector<TLabel> Labels;
};

struct TSolomonAgentBackend {
    TSolomonAgentBackend(const TString &handler = "http://localhost:3443/update")
        : Handler(handler)
    {
    }

    TString PrepareData(const TSensorSettings &global, const TSensor &sensor) const;
    TString DebugRequest(const TSensorSettings &global, const TVector<TSensor> &sensors) const;
    void UpdateData(const TSensorSettings &global, const TVector<TSensor> &sensors) const;

public:
    TString Handler;
};

class TSolomonPushBackend {
    void WriteCommons(NJson::TJsonWriter &writer, const TSensorSettings &global) const;
    void WriteSensors(NJson::TJsonWriter &writer, const TSensorSettings &global, const TVector<TSensor> &sensors) const;
    static const TString GetToken();

public:
    TSolomonPushBackend(const TString &project, const TString &cluster, const TString &host = GetLocalFQDN(), const TString &handler = "http://solomon.yandex.net/api/v2/push");
    TString PrepareData(const TSensorSettings &global, const TVector<TSensor> &sensors) const;
    TString DebugRequest(const TSensorSettings &global, const TVector<TSensor> &sensors) const;
    void UpdateData(const TSensorSettings &global, const TVector<TSensor> &sensors) const;

public:
    TString Project;
    TString Cluster;
    TString Host;
    TString Handler;
};

template<class TBackend = TSolomonAgentBackend>
struct TMonitoringAgent {
    TMonitoringAgent(const TBackend &backend = TBackend())
        : Backend(backend)
    {
    }

    inline TMonitoringAgent& Next(const TString &sensorName, double value) {
        Sensors.push_back(TSensor(sensorName, value));
        return *this;
    }

    inline TMonitoringAgent& Next(const TSensorData &data) {
        Sensors.push_back(TSensor(data));
        return *this;
    }

    inline TMonitoringAgent& Commit() {
        try {
            Backend.UpdateData(Global, Sensors);
        } catch (...) {
        }

        return *this;
    }

    inline TMonitoringAgent& Reset(double n = 0) {
        for (TSensor &sensor : Sensors) {
            sensor.Data.Reset(n);
        }

        return *this;
    }

    inline TString Debug() {
        return Backend.DebugRequest(Global, Sensors);
    }

    inline TMonitoringAgent& Derived() {
        if (!Sensors.empty()) {
            Sensors.back().ModeDerived = true;
        }
        return *this;
    }

    inline TMonitoringAgent& GlobalDerived() {
        Global.ModeDerived = true;
        return *this;
    }

    inline TMonitoringAgent& GlobalService(const TString &serviceName) {
        Global.ServiceName = serviceName;
        return *this;
    }

    inline TMonitoringAgent& Service(const TString &serviceName) {
        if (!Sensors.empty()) {
            Sensors.back().ServiceName = serviceName;
        }
        return *this;
    }

    inline TMonitoringAgent& Label(const TString &key, const TString &value) {
        if (!Sensors.empty()) {
            Sensors.back().Labels.push_back(TSensor::TLabel(key, value));
        }
        return *this;
    }

    inline TMonitoringAgent& GlobalTTL(int ttl) {
        Global.Ttl = ttl;
        return *this;
    }

    inline TMonitoringAgent& TTL(int ttl) {
        if (!Sensors.empty()) {
            Sensors.back().Ttl = ttl;
        }
        return *this;
    }

    inline TMonitoringAgent& GlobalTimestamp(TInstant ts) {
        Global.Timestamp = ts;
        return *this;
    }

    inline TMonitoringAgent& Timestamp(TInstant ts) {
        if (!Sensors.empty()) {
            Sensors.back().Timestamp = ts;
        }
        return *this;
    }

private:
    TVector<TSensor> Sensors;
    TSensorSettings Global;
    TBackend Backend;
};

struct TMonitorBase : public IThreadFactory::IThreadAble, public TNonCopyable {
    typedef TSimpleSharedPtr<TMonitorBase> Ptr;

    static void AtExitHandler(void *ptr);

public:
    TMonitorBase(int period = 1);
    ~TMonitorBase() override;
    void DoExecute() override;
    virtual void Shutdown();
    virtual void Upload();

protected:
    bool Active;
    int Period;
    TAutoPtr<IThreadFactory::IThread> ThreadRef;
    static TAtomic Instances;
};
