#pragma once

#include <balancer/kernel/io/iobase.h>
#include <balancer/kernel/memory/chunks.h>
#include <balancer/kernel/memory/split.h>

#include <library/cpp/coroutine/engine/impl.h>

#include <util/datetime/base.h>
#include <util/generic/buffer.h>
#include <util/stream/buffer.h>

namespace NModDebug {
    using namespace NSrvKernel;

    struct TDebugIoParams {
        TDuration Delay;
        size_t Size = Max<size_t>();
        size_t FailAfter = Max<size_t>();

    public:
        TDebugIoParams() {}

        TDebugIoParams(TDuration dur, size_t sz, size_t failAfter)
            : Delay(dur)
            , Size(sz)
            , FailAfter(failAfter)
        {}

        auto AsTuple() const {
            return std::make_tuple(Delay, Size, FailAfter);
        }

        explicit operator bool() const noexcept {
            return AsTuple() != TDebugIoParams().AsTuple();
        }
    };


    class TDebugIoBase {
    public:
        TDebugIoBase(TContExecutor& executor, TDebugIoParams params)
            : Exec_(executor)
            , Params_(params)
            , Health_(params.FailAfter)
        {}

    protected:
        TErrorOr<TChunkList> Next(TChunkList& lst) noexcept {
            if (!Health_) {
                return Y_MAKE_ERROR(TSystemError(ECONNRESET));
            }
            auto next = CutPrefix(std::min(Params_.Size, Health_), lst);
            Health_ -= std::min(Health_, next.size());
            TrySleep();
            return next;
        }

        void TrySleep() const noexcept {
            if (Params_.Delay) {
                Exec_.Running()->SleepT(Params_.Delay);
            }
        }

    private:
        TContExecutor& Exec_;
        const TDebugIoParams Params_;
        size_t Health_ = 0;
    };


    class TDebugIn: public IIoInput, public TDebugIoBase {
    public:
        TDebugIn(IIoInput& s, TContExecutor& executor, TDebugIoParams params) noexcept
            : TDebugIoBase(executor, params)
            , S_(s)
        {}

        TError DoRecv(TChunkList& lst, TInstant deadline) noexcept override {
            if (Buf_.Empty()) {
                Y_PROPAGATE_ERROR(S_.Recv(Buf_, deadline));
            }
            // will not produce an empty chunk list except if given one
            Y_PROPAGATE_ERROR(Next(Buf_).AssignTo(lst));
            return {};
        }

    private:
        IIoInput& S_;
        TChunkList Buf_;
    };


    class TDebugOut final : public IHttpOutput, public TDebugIoBase {
    public:
        TDebugOut(IHttpOutput& s, TContExecutor& executor, TDebugIoParams params) noexcept
            : TDebugIoBase(executor, params)
            , S_(s)
        {}

        TError DoSendHead(TResponse&& response, const bool forceClose, TInstant deadline) override {
            TrySleep();
            return S_.SendHead(std::move(response), forceClose, deadline);
        }

        TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
            if (lst.Empty()) {
                // eof
                Y_PROPAGATE_ERROR(Next(lst).AssignTo(lst));
                TrySleep();
                Y_PROPAGATE_ERROR(S_.Send(std::move(lst), deadline));
            } else {
                while (!lst.Empty()) {
                    // will not produce an empty chunk list except if given one
                    TChunkList buf;
                    Y_PROPAGATE_ERROR(Next(lst).AssignTo(buf));
                    Y_PROPAGATE_ERROR(S_.Send(std::move(buf), deadline));
                }
            }

            return {};
        }

        TError DoSendTrailers(THeaders&& trailers, TInstant deadline) override {
            return S_.SendTrailers(std::move(trailers), deadline);
        }

    private:
        IHttpOutput& S_;
    };
}
