#pragma once

#include <balancer/kernel/http/parser/http.h>
#include <library/cpp/coroutine/engine/events.h>
#include <balancer/kernel/stats/manager.h>
#include <balancer/kernel/module/conn_descr.h>


namespace NSrvKernel {
    class IModule;
    template <class> class INodeHandle;

    template <typename SlaveIo>
    class TTouchTrackingHttpOutput : public IHttpOutput {
    public:
        TTouchTrackingHttpOutput(SlaveIo& slave, const bool& sentSomething, size_t hedgedIndex, TMaybe<size_t>& whoTouched)
            : Slave_(slave)
            , SentSomething_(sentSomething)
            , HedgedIndex_(hedgedIndex)
            , WhoTouched_(whoTouched)
        {}

        TError CheckState() noexcept {
            if (SentSomething_ && WhoTouched_ != HedgedIndex_) {
                return Y_MAKE_ERROR(TTouchedOutputError{HedgedIndex_});
            }

            WhoTouched_ = HedgedIndex_;

            return {};
        }

        TError DoSendHead(TResponse&& response, const bool forceClose, TInstant deadline) noexcept override {
            Y_PROPAGATE_ERROR(CheckState());
            return Slave_.SendHead(std::move(response), forceClose, deadline);
        }

        TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
            Y_PROPAGATE_ERROR(CheckState());
            return Slave_.Send(std::move(lst), deadline);
        }

        TError DoSendTrailers (THeaders&& headers, TInstant deadline) override {
            Y_PROPAGATE_ERROR(CheckState());
            return Slave_.SendTrailers(std::move(headers), deadline);
        }

        bool Touched() const noexcept {
            return WhoTouched_ == HedgedIndex_;
        }

    private:
        SlaveIo& Slave_;

        // SentSomething_ is controlled by underlying stream
        const bool& SentSomething_;

        size_t HedgedIndex_ = 0;
        TMaybe<size_t>& WhoTouched_;
    };

    class IBackends;

    enum class EHedgedStatus {
        Wait,
        Ready,
        Cancelled
    };

    class TAttemptsHolderBase : public IAttemptsHolder {
    public:
        TAttemptsHolderBase(ui32 attempts, ui32 fastAttempts, bool useEncryption = false)
            : AttemptsMax_(attempts)
            , AttemptsAvailable_(attempts)
            , FastAttemptsAvailable_(fastAttempts)
            , UseEncryption_(useEncryption)
        {}

        size_t AttemptsMax() const override {
            return AttemptsMax_;
        }
        size_t AttemptsAvailable() const override {
            return AttemptsAvailable_;
        }
        size_t FastAttemptsAvailable() const override {
            return FastAttemptsAvailable_;
        }
        void ResetAttempts() override {
            AttemptsAvailable_ = 0;
        }
        void ResetDcAttempts() override {
        }
        ui64 RegisterAttempt() override {
            --AttemptsAvailable_;
            return 0;
        }
        void UnregisterAttempt() override {
            ++AttemptsAvailable_;
        }
        bool RegisterFail(const TError& error, bool fast503, bool /*hedgedRequest*/, ui64 /*id*/) override {
            bool fastError = false;
            if (auto* e = error.GetAs<TBackendError>()) {
                if (const auto* ee = e->InnerError().GetAs<THttpError>()) {
                    fastError = fast503 && ee->Code() == 503;
                }

                fastError = e->FastError().GetOrElse(fastError);
            } else if (const auto* e = error.GetAs<TNetworkResolutionError>()) {
                //TNetworkResolutionError is fast error
                fastError = true;
            }

            if (fastError && FastAttemptsAvailable_) {
                --FastAttemptsAvailable_;
                ++AttemptsAvailable_;
            }

            return fastError;
        }

        bool UseEncryption() const override { return UseEncryption_; }

        bool WaitReadyForHedged(TCont* cont) override {
            if (HedgedStatus_ == EHedgedStatus::Wait) {
                Y_ASSERT(!HedgedStatusEvent_);
                HedgedStatusEvent_ = MakeHolder<TContEvent>(cont);
                HedgedStatusEvent_->WaitD(TInstant::Max());
                HedgedStatusEvent_.Destroy();
            }

            return HedgedStatus_ == EHedgedStatus::Ready;
        }

        void ReadyForHedged() override {
            HedgedStatus_ = EHedgedStatus::Ready;
            if (HedgedStatusEvent_) {
                HedgedStatusEvent_->Wake();
            }
        }

        void CancelHedged() override {
            HedgedStatus_ = EHedgedStatus::Cancelled;
            if (HedgedStatusEvent_) {
                HedgedStatusEvent_->Wake();
            }
        }

    protected:
        const ui32 AttemptsMax_ = 0;
        ui32 AttemptsAvailable_ = 0;
        ui32 FastAttemptsAvailable_ = 0;
        const bool UseEncryption_;

        EHedgedStatus HedgedStatus_ = EHedgedStatus::Wait;
        THolder<TContEvent> HedgedStatusEvent_;
    };
}



namespace NModBalancer {
    using namespace NSrvKernel;

    class TAttempts {
    public:
        explicit TAttempts(size_t value);

        TAttempts(const TAttempts&) = default;

        size_t Get(size_t backendsCount) const;

        void ParseConfig(TStringBuf v);
        void Override(TStringBuf v);

        size_t GetOriginalValue() const {
            return OriginalValue_;
        }
    private:
        size_t OriginalValue_ = 1;
        size_t Value_ = OriginalValue_;

        bool IsOverriden_ = false;
        bool UseBackendsCount_ = false;
    };

    INodeHandle<IModule>* Handle();
}
