#pragma once

#include <balancer/kernel/custom_io/null.h>
#include <balancer/kernel/module/iface.h>
#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/http/parser/httpdecoder.h>

namespace NSrvKernel {

class TRequester {
public:
    TRequester(const IModule& submodule, const TConnDescr& mainDescr) noexcept;

    /**
     * Construct and send a GET-like request, ignore response completely
     **/
    TError Request(TRequest&& request) const noexcept;

    /**
     * Construct and send a GET-like request and receive response headers
     **/
    TError Request(TRequest&& request, TResponse& response) const noexcept;

    /**
     * Construct and send a GET-like request and receive full response.
     **/
    TError Request(TRequest&& request, TResponse& response, TChunkList& responseBody) const noexcept;

    /**
     * Construct and send a POST-like request, ignore response completely.
     **/
    TError Request(TRequest&& request, TChunkList&& requestBody, bool chunked) const noexcept;

    /**
     * Construct and send a chunked POST-like request, ignore response completely.
     **/
    TError Request(TRequest&& request, IIoInput& in, bool haveFullBody = false) const noexcept;

    /**
     * Construct and send a POST-like request and receive response headers.
     **/
    TError Request(
        TRequest&& request, TChunkList&& requestBody, bool chunked,
        TResponse& response
    ) const noexcept;

    /**
     * Construct and send a POST-like request and receive full response.
     **/
    TError Request(
        TRequest&& request, TChunkList&& requestBody, bool chunked,
        TResponse& response, TChunkList& responseBody
    ) const noexcept;

    /**
     * Construct and send a chunked POST-like request and receive full response.
     **/
    TError Request(
        TRequest&& request, IIoInput& in,
        TResponse& response, TChunkList& responseBody,
        bool haveFullBody = false
    ) const noexcept;

    /**
     * Send tcp request
     **/
    TError TcpRequest(IIoInput& in, IIoOutput& out) const noexcept;

public:
    TRequester(
        const IModule& submodule,
        TTcpConnProps& tcpProps,
        TInstant start,
        TAccessLogOutput accessLog,
        TLog* errorLog,
        TRequestHash hash
    ) noexcept;

private:
    TError RequestImpl(TRequest&& request, IIoInput& in, bool haveFullBody) const noexcept;

    TError RequestImpl(
        TRequest&& request, IIoInput& in,
        TResponse& response, TChunkList& responseBody,
        bool haveFullBody
    ) const noexcept;

    TError RequestImpl(
        TRequest&& request, IIoInput& in,
        TResponse& response, IIoOutput& out,
        bool haveFullBody
    ) const noexcept;

    TError RequestImpl(
        TRequest* request, IIoInput& in, IHttpOutput& out, bool haveFullBody
    ) const noexcept;

private:
    const IModule& Submodule_;
    TTcpConnProps& TcpProps_;
    TInstant Start_;
    TAccessLogOutput ExtraAccessLog_;
    TLog* ErrorLog_ = nullptr;
    TRequestHash Hash_ = 0; // For hash balancing
};

/**
 * Sending HTTP requests without TConnDescr.
 * Doesn't write accesslog. Hash balancing doesn't work.
 *
 * Usage example:
 *   TLog errorLog;
 *   TAsyncRequester requester(submodule, errorLog);
 *
 *   TRequest request;
 *   TResponse response;
 *   requester.Request(std::move(request), response);
 **/
class TAsyncRequester {
public:
    TAsyncRequester(const IModule& submodule, TLog* errorLog, IWorkerCtl& process, TRequestHash hash = 0, TInstant start = {})
        : TcpProps_(process, RemoteAddress_, LocalAddress_, nullptr)
        , Requester_(submodule, TcpProps_, start, {}, errorLog, hash)
    {}

    TRequester& Requester() {
        return Requester_;
    }

    TTcpConnProps& Props() {
        return TcpProps_;
    }

private:
    TAddrHolder RemoteAddress_{ &TDummyAddr::Instance() };
    TAddrHolder LocalAddress_{ &TDummyAddr::Instance() };
    TTcpConnProps TcpProps_;
    TRequester Requester_;
};

}
