#include <saas/common_proxy/lib/client/saas_send.h>
#include <saas/library/sender_neh/sender_neh.h>
#include <kernel/common_proxy/unistat_signals/signals.h>

namespace {
    using namespace NCommonProxy;

    class TDocContext : public NRTYServer::IHandleListener, public NRTYServer::TMultiRequesterBase::TRequestData {
    public:
        TDocContext(const TString& name, TAtomic& inFly, TDuration timeout, IReplier::TPtr replier)
            : Name(name)
            , InFly(inFly)
            , Timeout(timeout)
            , Replier(replier)
            , Start(Now())
        {
            AtomicIncrement(InFly);
        }

        ~TDocContext() {
            TCommonProxySignals::PushSpecialSignal(Name, "send_time", (Now() - Start).MilliSeconds());
            AtomicDecrement(InFly);
        }

        virtual void OnStart(const NRTYServer::TQueryInfo& /*info*/) const override {
        }

        virtual void OnNotify(const NRTYServer::TQueryInfo& /*info*/, NNeh::TResponseRef& resp) const override {
            NSaas::TSendResult result = ParseReply(resp.Get());
            Replier->AddReply(Name, result.GetHttpCode(), result.GetMessage());
        }

        virtual void OnCancel(const NRTYServer::TQueryInfo& info, const TString& reason) const override {
            Replier->AddReply(Name, 500, reason + "(" + ToString(info.Attempts) + ", " + info.Duration.ToString() + ")");
        }

        virtual TDuration GetRecvTimeout() const override {
            return Timeout;
        }

        virtual bool OnResend(const NRTYServer::TQueryInfo& /*info*/, TString& /*newAdrr*/, const NNeh::TMessage& /*msg*/, const NNeh::TResponse* resp) const override {
            NSaas::TSendResult result = ParseReply(resp);
            if (result.ShouldRetryMessage() || result.GetHttpCode() >= 500) {
                TCommonProxySignals::PushSpecialSignal(Name, "resend", 1);
                return true;
            }

            return false;
        }

    private:
        NSaas::TSendResult ParseReply(const NNeh::TResponse* resp) const {
            if (!resp) {
                return NSaas::TSendResult::FromProxyReply(500, Default<TString>());
            }
            if (resp->IsError()) {
                return NSaas::TSendResult::FromProxyReply(resp->GetErrorCode(), resp->Data ? resp->Data : resp->GetErrorText());
            }
            return NSaas::TSendResult::FromProxyReply(200, resp->Data);
        }

        const TString Name;
        TAtomic& InFly;
        const TDuration Timeout;
        IReplier::TPtr Replier;
        TInstant Start;
    };
}

namespace NCommonProxy {

    TSaasSendingClient::TSaasSendingClient(const TString& name, const TSenderConfig& config)
        : ISaasSendingClient(name)
        , Config(config)
        , NehAddress("full2://" + Config.Host + ":" + ToString(Config.Port) + "/")
        , IndexingClient(Config.Host, Config.Port, Config.Url, Config.Timeout)
        , NehRequester(Config.AttemptsCount, Config.ResendDurations)
    {
    }

    void TSaasSendingClient::Start() {
        NehRequester.Start(Config.AsyncSendThreads);
    }

    void TSaasSendingClient::Stop() {
        NehRequester.Stop(true);
        if (!Config.SendParams.Realtime) {
            NSaas::TAction reopen;
            reopen.SetActionType(NSaas::TAction::atReopen);
            IndexingClient.Send(reopen, Config.SendType, Config.SendParams);
        }
    }

    void TSaasSendingClient::Wait() const {
        while (AtomicGet(InFly)) {
            Sleep(TDuration::MilliSeconds(100));
        }
    }

    ui32 TSaasSendingClient::GetInFly() const {
        return AtomicGet(InFly);
    }
    namespace {
        const TString ASYNC_SUFFIX = "_async";
    }

    void TSaasSendingClient::Send(const NSaas::TAction& action, IReplier::TPtr replier) const {
        if (Config.SendType.EndsWith(ASYNC_SUFFIX)) {
            const TString type = Config.SendType.substr(0, Config.SendType.length() - ASYNC_SUFFIX.length());
            NNeh::TMessage msg(NehAddress, IndexingClient.GetHttpMessage(action,  type, Config.SendParams));

            if (Config.MaxInFly > 0 && AtomicGet(InFly) > Config.MaxInFly) {
                do {
                    Sleep(TDuration::MilliSeconds(10));
                } while (AtomicGet(InFly) > Config.MaxInFly / 2);
            }
            auto context = MakeIntrusive<TDocContext>(Name, InFly, Config.Timeout * 1.1, replier);
            NehRequester.Send(std::move(msg), context.Get(), context);
        } else {
            NSaas::TSendResult result = IndexingClient.Send(action, Config.SendType, Config.SendParams);
            replier->AddReply(Name, result.GetHttpCode(), result.GetMessage());
        }
    }

    ISaasSendingClient::TSendResult TSaasSendingClient::Send(const NSaas::TAction& action) const {
        TString st = Config.SendType;
        if (st.EndsWith(ASYNC_SUFFIX)) {
            st = st.substr(0, st.length() - ASYNC_SUFFIX.length());
        }
        NSaas::TSendResult result = IndexingClient.Send(action, st, Config.SendParams);
        return TSendResult(result.IsSucceeded(), result.GetMessage());
    }

    void TSaasSendingClient::UpdateUnistatSignals() const {
        TCommonProxySignals::PushSpecialSignal(Name, "in_fly", AtomicGet(InFly));
        TCommonProxySignals::PushSpecialSignal(Name, "in_sender_fly", NehRequester.QueueSize());
        TCommonProxySignals::PushSpecialSignal(Name, "sender_queue", NehRequester.SendQueueSize());
    }

    void TSaasSendingClient::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, "in_sender_fly"), "ammv", NUnistat::TPriority(50), NUnistat::TStartValue(0), EAggregationType::LastValue);
        tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Name, "sender_queue"), "ammv", NUnistat::TPriority(50), NUnistat::TStartValue(0), EAggregationType::LastValue);
        tass.DrillFloatHole(TCommonProxySignals::GetSignalName(Name, "resend"), "dmmm", NUnistat::TPriority(50));
    }
}
