#pragma once

#include <balancer/kernel/client_request/face.h>
#include <balancer/kernel/process/master_main.h>
#include <balancer/modules/factory/common.h>

#include <library/cpp/threading/future/future.h>
#include <util/datetime/base.h>

namespace NBalancerClient {

    struct TBalancerClientResponse {
        NSrvKernel::TResponse HttpResponse;
        TString Data;
        NSrvKernel::THeaders Trailers;
    };

    class TBalancerClientResponseContext {
    public:
        template <typename... Args>
        TBalancerClientResponseContext(TString endpoint, Args&&... args)
            : Endpoint_(std::move(endpoint))
            , ResponseOrError_(std::forward<Args>(args)...)
        {}

        NSrvKernel::TErrorOr<TBalancerClientResponse>& GetResponse() {
            return ResponseOrError_;
        }

        const TString& Endpoint() const {
            return Endpoint_;
        }

    private:
        TString Endpoint_; // ip:port
        NSrvKernel::TErrorOr<TBalancerClientResponse> ResponseOrError_;
    };

    class TRequestContext : public NSrvKernel::IChannelRequest {
    public:
        TRequestContext(NSrvKernel::IModule& entry,
                        NSrvKernel::TRequest request,
                        TString data,
                        THolder<NSrvKernel::IAttemptsHolder> attemptsHolder,
                        NSrvKernel::IClientRequestRef clientRequest,
                        TAtomicSharedPtr<NSrvKernel::TU2WChannel<TString>> inputChannel,
                        TAtomicSharedPtr<NSrvKernel::TW2UChannel<TString>> outputChannel,
                        NThreading::TPromise<TBalancerClientResponseContext> promise,
                        TLog* errorLog)
            : NSrvKernel::IChannelRequest(entry,
                                          std::move(request),
                                          std::move(data),
                                          std::move(attemptsHolder),
                                          std::move(clientRequest),
                                          std::move(inputChannel),
                                          std::move(outputChannel),
                                          errorLog)
            , Promise(promise)
        {}

        void DoSetResponseValue(TString endpoint, NSrvKernel::TResponse response, TString data, NSrvKernel::THeaders trailers) override {
            TBalancerClientResponse resp{std::move(response), std::move(data), std::move(trailers)};
            Promise.SetValue(TBalancerClientResponseContext(std::move(endpoint), std::move(resp)));
        }

        void DoSetResponseError(TString endpoint, NSrvKernel::TError error) override {
            Promise.SetValue(TBalancerClientResponseContext(std::move(endpoint), std::move(error)));
        }

        NThreading::TFuture<TBalancerClientResponseContext> GetFuture() {
            return Promise;
        }

    private:
        NThreading::TPromise<TBalancerClientResponseContext> Promise;
    };

    class TBalancerClient {
    public:
        TBalancerClient(TAtomicSharedPtr<NSrvKernel::NProcessCore::TMainTask> mainTask,
            NSrvKernel::TNodeFactory<NSrvKernel::IModule>& modulesFactory, const TString& moduleName,
            const NJson::TJsonValue& jsonConfig);

        ~TBalancerClient();

        THolder<TRequestContext>
        SendRequest(NSrvKernel::TRequest request, TString data, THolder<NSrvKernel::IAttemptsHolder> attemptsHolder,
                    TAtomicSharedPtr<NSrvKernel::TU2WChannel<TString>> inputChannel, bool inplace = false, TLog* errorLog = nullptr);

        THolder<TRequestContext>
        SendRequest(NSrvKernel::IClientRequestRef request, THolder<NSrvKernel::IAttemptsHolder> attemptsHolder,
                    TAtomicSharedPtr<NSrvKernel::TU2WChannel<TString>> inputChannel, bool inplace = false, TLog* errorLog = nullptr);

        THolder<TRequestContext>
        SendRequest(NSrvKernel::TRequest request,
                    TString data,
                    NSrvKernel::IClientRequestRef clientRequest,
                    THolder<NSrvKernel::IAttemptsHolder> attemptsHolder,
                    TAtomicSharedPtr<NSrvKernel::TU2WChannel<TString>> inputChannel,
                    TAtomicSharedPtr<NSrvKernel::TW2UChannel<TString>> outputChannel,
                    bool inplace = false,
                    TLog* errorLog = nullptr);

    private:
        NSrvKernel::TNodeFactory<NSrvKernel::IModule>& ModulesFactory_;
        TAtomicSharedPtr<NSrvKernel::NProcessCore::TMainTask> MainTask_;
        NSrvKernel::TChannelListener& ChannelListener_;
        THolder<NSrvKernel::TTree> Tree_;
    };
}
