#include "pq_send.h"
#include <saas/library/persqueue/writer/writer.h>
#include <saas/library/persqueue/logger/logger.h>
#include <kernel/common_proxy/unistat_signals/signals.h>
#include <util/system/env.h>

namespace {
    constexpr TStringBuf ENVIRONMENT_VARIABLE_PREFIX = "env.";

    TString ReadSecret(const TString& secretName) {
        if (!secretName.StartsWith(ENVIRONMENT_VARIABLE_PREFIX))
            return secretName;
        auto secret = secretName.substr(ENVIRONMENT_VARIABLE_PREFIX.length(), secretName.length());
        return GetEnv(secret);
    }
}

namespace NCommonProxy {

    class TPQSaasSendingClient::TWriter final: public NSaas::TPersQueueWriter {
    public:
        TWriter(const TPQSaasSendingClient& owner)
            : Owner(owner)
        {}
    protected:
        virtual void BeforeProcessWrite(const NRTYServer::TMessage& /*msg*/, const NPersQueue::TData& /*data*/, const TVector<NSearchMapParser::TShardsInterval>& shards) const override {
            for (const auto& shard : shards) {
                TCommonProxySignals::PushSpecialSignal(Owner.Name, "messages_" + shard.ToString(), 1);
            }
            TCommonProxySignals::PushSpecialSignal(Owner.Name, "messages", shards.size());
        }
        virtual void InitShards(const TVector<NSearchMapParser::TShardsInterval>& shards) const override {
            auto& tass = TUnistat::Instance();
            for (const auto& shard : shards) {
                tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Owner.Name, "messages_" + shard.ToString()), "dmmm", NUnistat::TPriority(50));
                tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Owner.Name, "bytes_" + shard.ToString()), "dmmm", NUnistat::TPriority(50));
                tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Owner.Name, "compressed_bytes_" + shard.ToString()), "dmmm", NUnistat::TPriority(50));
                tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Owner.Name, "attempts_" + shard.ToString()), "dmmm", NUnistat::TPriority(50));
            }
        }
    private:
        const TPQSaasSendingClient& Owner;
    };

    TPQSaasSendingClient::TPQSaasSendingClient(const TString& name, const TPQSenderConfig& config, const NSaas::TCheckMessageSettings& checkMessageSettings)
        : ISaasSendingClient(name)
        , Config(config)
        , CheckMessageSettings(checkMessageSettings)
        , Logger(Config.Log, (ELogPriority)Config.LogLevel)
    {}

    TPQSaasSendingClient::~TPQSaasSendingClient() {
    }

    void TPQSaasSendingClient::Start() {
        if (Config.Topic.empty()) {
            return;
        }
        NSaas::TSearchMapInputSettings searchMapSettings;
        searchMapSettings.DMHost = Config.DMHost;
        searchMapSettings.DMPort = Config.DMPort;
        searchMapSettings.Ctype = Config.CType;
        searchMapSettings.StaticaHost = Config.MDSHost;
        searchMapSettings.StaticaQuery = Config.CType;
        do {
            NSaas::TPersQueueWriterSettings writerSettings;
            Writer = MakeHolder<TWriter>(*this);
            try {
                TIntrusivePtr<NPersQueue::ILogger> pqLogger = MakeIntrusive<NSaas::TPersQueueLogger<NPersQueue::ILogger>>(Logger);
                writerSettings.SetLogger(pqLogger);
                writerSettings.SetSendTimeout(Config.SendTimeout);
                switch (Config.ShardsFrom) {
                case TPQSenderConfig::EShardsFrom::CONFIG:
                    writerSettings.SetServiceInfo(Config.Shards, Config.Sharding);
                    break;
                case TPQSenderConfig::EShardsFrom::DM:
                    writerSettings.SetServiceInfo(searchMapSettings, Config.ServiceName);
                    break;
                }
                NPersQueue::TPQLibSettings pqLibSettings;
                pqLibSettings.ThreadsCount = Config.PQLibThreads;
                pqLibSettings.CompressionPoolThreads = Config.PQLibCompressThreads;
                pqLibSettings.GRpcThreads = Config.PQLibGRpcThreads;
                writerSettings.SetTvm(Config.TVMClientID, Config.TVMServerID, ReadSecret(Config.TVMSecret));
                writerSettings.SetPersQueueSettings(Config.PQHost, Config.Topic, pqLibSettings);
                writerSettings.SetThreadsBlockingMode(true);
                writerSettings.SetMaxInFlightCount(Config.MaxInFly);
                writerSettings.SetThreadsName(Name);
                if (Config.WriteThreads) {
                    writerSettings.SetWriteThreadsCount(Config.WriteThreads);
                } else {
                    writerSettings.SetNoWriteQueue(true);
                }
                writerSettings.SetPrepareMessages(false);
                writerSettings.SetDryRun(Config.DryRun);
                if (Config.Codec) {
                    writerSettings.SetCodec(*Config.Codec);
                }
                writerSettings.SetCheckMessagesSettings(CheckMessageSettings);
                Writer->Init(writerSettings);
            } catch (...) {
                ERROR_LOG << Name << ": Error in creating: " << CurrentExceptionMessage() << Endl;
                Writer.Reset();
            }
        } while (!Writer);
    }

    void TPQSaasSendingClient::Stop() {
        Writer.Reset();
    }

    void TPQSaasSendingClient::Wait() const {
    }

    ui32 TPQSaasSendingClient::GetInFly() const {
        return Writer ? Writer->GetInFlight() : 0;
    }

    void TPQSaasSendingClient::Send(const NSaas::TAction& action, IReplier::TPtr replier) const {
        if (!Writer) {
            return;
        }
        TString url = action.HasDocument() ? action.GetDocument().GetUrl() : Default<TString>();
        TInstant start = Now();
        Writer->Write(action).Subscribe([this, url, start, replier](const TWriter::TWriteResultFuture& future) {
            const NSaas::TPersQueueWriter::TWriteResult& result = future.GetValueSync();
            if (!result.Written) {
                replier->AddReply(Name, 500, url + ":" + result.Comment);
            }
            ui32 size = 0;
            ui32 compressedSize = 0;
            for (const auto& attempt: result.Attempts) {
                size += attempt.Size;
                compressedSize += attempt.CompressedSize;
                TCommonProxySignals::PushSpecialSignal(Name, "bytes_" + attempt.Shard, attempt.Size);
                TCommonProxySignals::PushSpecialSignal(Name, "compressed_bytes_" + attempt.Shard, attempt.CompressedSize);
                TCommonProxySignals::PushSpecialSignal(Name, "attempts_" + attempt.Shard, 1);
            }
            TCommonProxySignals::PushSpecialSignal(Name, "bytes", size);
            TCommonProxySignals::PushSpecialSignal(Name, "compressed_bytes", compressedSize);
            TCommonProxySignals::PushSpecialSignal(Name, "attempts", result.Attempts.size());
            TCommonProxySignals::PushSpecialSignal(Name, "send_time", (Now() - start).MilliSeconds());
        });
    }

    ISaasSendingClient::TSendResult TPQSaasSendingClient::Send(const NSaas::TAction& action) const {
        if (!Writer) {
            return TSendResult(false, Name + " not initialized");
        }
        auto res = Writer->Write(action).GetValueSync();
        return TSendResult(res.Written, res.Comment);
    }

    void TPQSaasSendingClient::UpdateUnistatSignals() const {
        TCommonProxySignals::PushSpecialSignal(Name, "in_fly", GetInFly());
    }

    void TPQSaasSendingClient::RegisterSignals(TUnistat& tass) const {
        tass.DrillHistogramHole(TCommonProxySignals::GetSignalName(Name, "send_time"), "dhhh", NUnistat::TPriority(50), TCommonProxySignals::TimeIntervals);
        tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Name, "in_fly"), "ammv", NUnistat::TPriority(50), NUnistat::TStartValue(0), EAggregationType::LastValue);
        tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Name, "messages"), "dmmm", NUnistat::TPriority(50));
        tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Name, "bytes"), "dmmm", NUnistat::TPriority(50));
        tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Name, "compressed_bytes"), "dmmm", NUnistat::TPriority(50));
        tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Name, "attempts"), "dmmm", NUnistat::TPriority(50));
    }

}
