#include "requester.h"

namespace NSrvKernel {

    namespace {
        TChunksInput PrepareRequest(TRequest& req, TChunkList&& lst, bool chunked) noexcept {
            req.Props().ChunkedTransfer = chunked;
            if (chunked) {
                req.Props().ContentLength.reset();
            } else {
                req.Props().ContentLength = lst.size();
            }
            return TChunksInput{std::move(lst)};
        }

        void PrepareRequest(TRequest& req, IIoInput&) noexcept {
            req.Props().ChunkedTransfer = true;
            req.Props().ContentLength.reset();
        }

        TNullStream PrepareRequest(TRequest& req) noexcept {
            req.Props().ChunkedTransfer = false;
            req.Props().ContentLength.reset();
            return {};
        }
    }

    TRequester::TRequester(const IModule& submodule, const TConnDescr& mainDescr) noexcept
        : TRequester(
            submodule,
            mainDescr.Properties->Parent,
            mainDescr.Properties->Start,
            mainDescr.ExtraAccessLog,
            mainDescr.ErrorLog,
            mainDescr.Hash
        )
    {}

    TRequester::TRequester(
        const IModule& submodule,
        TTcpConnProps& tcpProps,
        TInstant start,
        TAccessLogOutput accessLog,
        TLog* errorLog,
        TRequestHash hash
    ) noexcept
        : Submodule_(submodule)
        , TcpProps_(tcpProps)
        , Start_(start)
        , ExtraAccessLog_(accessLog)
        , ErrorLog_(errorLog)
        , Hash_(hash)
    {}

    TError TRequester::Request(TRequest&& request) const noexcept {
        auto in = PrepareRequest(request);
        return RequestImpl(std::move(request), in, true);
    }

    TError TRequester::Request(TRequest&& request, TResponse& response) const noexcept {
        auto in = PrepareRequest(request);
        TNullStream out;
        return RequestImpl(std::move(request), in, response, out, true);
    }

    TError TRequester::Request(
        TRequest&& request,
        TResponse& response, TChunkList& responseBody
    ) const noexcept {
        auto in = PrepareRequest(request);
        return RequestImpl(std::move(request), in, response, responseBody, true);
    }

    TError TRequester::Request(
        TRequest&& request, TChunkList&& requestBody, bool chunked
    ) const noexcept {
        auto in = PrepareRequest(request, std::move(requestBody), chunked);
        return RequestImpl(std::move(request), in, true);
    }

    TError TRequester::Request(TRequest&& request, IIoInput& in, bool haveFullBody) const noexcept {
        PrepareRequest(request, in);
        return RequestImpl(std::move(request), in, haveFullBody);
    }

    TError TRequester::Request(
        TRequest&& request, TChunkList&& requestBody, bool chunked,
        TResponse& response
    ) const noexcept {
        auto in = PrepareRequest(request, std::move(requestBody), chunked);
        TNullStream out;
        return RequestImpl(std::move(request), in, response, out, true);
    }

    TError TRequester::Request(
        TRequest&& request, TChunkList&& requestBody, bool chunked,
        TResponse& response, TChunkList& responseBody
    ) const noexcept {
        auto in = PrepareRequest(request, std::move(requestBody), chunked);
        return RequestImpl(std::move(request), in, response, responseBody, true);
    }

    TError TRequester::Request(
        TRequest&& request, IIoInput& in,
        TResponse& response, TChunkList& responseBody,
        bool haveFullBody
    ) const noexcept {
        PrepareRequest(request, in);
        return RequestImpl(std::move(request), in, response, responseBody, haveFullBody);
    }

    TError TRequester::TcpRequest(IIoInput& in, IIoOutput& out) const noexcept {
        TResponse response;
        auto output = MakeHttpOutput([](TResponse&&, const bool, TInstant) -> TError {
            Y_FAIL("SendHead in tcp proxy mode");
        }, [&out](TChunkList lst, TInstant deadline) {
            return out.Send(std::move(lst), deadline);
        }, [] (THeaders&&, TInstant) -> TError {
            Y_FAIL("SendTrailers in tcp proxy mode");
        });

        return RequestImpl(nullptr, in, output, false);
    }

    TError TRequester::RequestImpl(TRequest&& request, IIoInput& in, bool haveFullBody) const noexcept {
        TResponse response;
        TNullStream out;
        return RequestImpl(std::move(request), in, response, out, haveFullBody);
    }

    TError TRequester::RequestImpl(
        TRequest&& request, IIoInput& in,
        TResponse& response, TChunkList& responseBody,
        bool haveFullBody
    ) const noexcept {
        TChunksOutput out;
        Y_PROPAGATE_ERROR(RequestImpl(std::move(request), in, response, out, haveFullBody));
        responseBody = std::move(out.Chunks());
        return {};
    }

    TError TRequester::RequestImpl(
        TRequest&& request, IIoInput& in,
        TResponse& response, IIoOutput& out,
        bool haveFullBody
    ) const noexcept {
        // TODO (smalukav): rework BALANCER-2214
        request.Props().HTTP2 = nullptr;

        auto storeOutput = MakeHttpOutput([&response](TResponse&& result, const bool, TInstant) {
            // thus we do not need to zero out the response
            response = std::move(result);
            return TError{};
        }, [&out](TChunkList lst, TInstant deadline) {
            return out.Send(std::move(lst), deadline);
        }, [] (THeaders&&, TInstant) -> TError {
            return TError{};
        });

        return RequestImpl(&request, in, storeOutput, haveFullBody);
    }

    TError TRequester::RequestImpl(
        TRequest* request, IIoInput& in, IHttpOutput& out, bool haveFullBody
    ) const noexcept {
        TConnProps props(TcpProps_, TInstant::Now(), 0, nullptr);

        TConnDescr descr(in, out, props);
        descr.ExtraAccessLog = ExtraAccessLog_;
        descr.ErrorLog = ErrorLog_;
        descr.Hash = Hash_;
        descr.Request = request;
        descr.HaveFullBody = haveFullBody;

        return Submodule_.Run(descr);
    }
}
