#pragma once

#include <balancer/kernel/http/parser/http.h>
#include <balancer/kernel/module/iface.h>
#include <balancer/kernel/client_request/face.h>

#include <balancer/kernel/coro/waked.h>

namespace NSrvKernel {

    struct IChannelRequest {
    public:
        IChannelRequest(IModule& entry,
                        TRequest request,
                        TString data,
                        THolder<IAttemptsHolder> attemptsHolder,
                        IClientRequestRef clientRequest,
                        TAtomicSharedPtr<TU2WChannel<TString>> inputChannel,
                        TAtomicSharedPtr<TW2UChannel<TString>> outputChannel,
                        TLog* errorLog = nullptr)
            : Entry(entry)
            , Request(std::move(request))
            , Data(std::move(data))
            , AttemptsHolder_(std::move(attemptsHolder))
            , ClientRequest_(std::move(clientRequest))
            , InputChannel_(std::move(inputChannel))
            , OutputChannel_(std::move(outputChannel))
            , ErrorLog_(errorLog)
        {}

        virtual ~IChannelRequest() {}

        virtual void DoSetResponseValue(TString endpoint, TResponse response, TString data, THeaders trailers) = 0;
        virtual void DoSetResponseError(TString endpoint, TError error) = 0;

        void Cancel() {
            if (Cont_) {
                Cont_->Cancel();
                Cont_ = nullptr;
            }
        }

        void RunRequest(IWorkerCtl* worker);

        IAttemptsHolder& GetAttemptsHolder() const;

    private:
        void SetResponseValue(TString endpoint, TResponse response, TString data, THeaders trailers) {
            Cont_ = nullptr;
            DoSetResponseValue(std::move(endpoint), std::move(response), std::move(data), std::move(trailers));
        }

        void SetResponseError(TString endpoint, TError error) {
            Cont_ = nullptr;
            DoSetResponseError(std::move(endpoint), std::move(error));
        }

        void Run(TCont*);
        void DoRun();

        IModule& Entry;
        TRequest Request;
        TString Data;
        THolder<IAttemptsHolder> AttemptsHolder_;
        IClientRequestRef ClientRequest_;
        TAtomicSharedPtr<TU2WChannel<TString>> InputChannel_;
        TAtomicSharedPtr<TW2UChannel<TString>> OutputChannel_;
        IWorkerCtl* Worker_ = nullptr;
        TCont* Cont_ = nullptr;
        TLog* ErrorLog_ = nullptr;
    };

    class TChannelListener {
    public:
        explicit TChannelListener(size_t workersCount);

        TU2WChannel<IChannelRequest*>& GetChannel(size_t id);

        EChannelStatus SendRequest(IChannelRequest* request);

    private:
        TU2WChannel<IChannelRequest*>& ChooseChannel(size_t reqId);

    private:
        const size_t WorkersCount_ = 0;
        TAtomic CurrentRequestId_ = 0;
        TVector<THolder<TU2WChannel<IChannelRequest*>>> WorkersChannels_;
    };
}
