#include "http2_request.h"
#include "http2_backend.h"
#include "http2_stream.h"
#include "http2_connection.h"

#include <balancer/kernel/module/conn_descr.h>
#include <balancer/kernel/custom_io/iterator.h>

#include <nghttp2/nghttp2.h>

#include <util/generic/array_ref.h>

#include <array>
#include <contrib/libs/nghttp2/lib/nghttp2_callbacks.h>

using namespace NBalancerClient;
using namespace NSrvKernel;

THttp2Request::THttp2Request(TCallbacks callbacks)
    : Callbacks_{std::move(callbacks)}
{}

const TMaybe<THttp2RequestSummary>& THttp2Request::GetSummary() const noexcept {
    return Summary_;
}

TError THttp2Request::DoRun(const TConnDescr &descr, const TBackendProtocols &protocols, const THostInfo& hostInfo) noexcept {
    auto http2Backend = protocols.GetImpl<THttp2Backend>();
    Y_REQUIRE(http2Backend, yexception{} << "backend not found");

    THttp2Backend::TConnectionHolder connection;
    Y_PROPAGATE_ERROR(http2Backend->GetOrCreateConnection(descr, hostInfo).AssignTo(connection));

    if (descr.Request == nullptr) {
        return {};
    }

    if (descr.AttemptsHolder) {
        descr.AttemptsHolder->SetEndpoint(connection->ResolvedAddr(), descr.RequestType == ERequestType::Hedged);
    }

    THttp2Stream::TCallbacks callbacks;
    if (Callbacks_.OnChunkSent) {
        callbacks.OnChunkSent = [&] (size_t size) {
            Callbacks_.OnChunkSent(descr.Id, size);
        };
    }
    if (Callbacks_.OnChunkReceived) {
        callbacks.OnChunkReceived = [&] (size_t size) {
            Callbacks_.OnChunkReceived(descr.Id, size);
        };
    }
    if (Callbacks_.OnEndOfInputStream) {
        callbacks.OnEndOfInputStream = [&] () {
            Callbacks_.OnEndOfInputStream(descr.Id);
        };
    }
    if (Callbacks_.OnFrameSentHandler) {
        callbacks.OnFrameSentHandler = [&] (const nghttp2_frame* frame) {
            Callbacks_.OnFrameSentHandler(descr.Id, frame);
        };
    }
    if (Callbacks_.OnFrameRecvHandler) {
        callbacks.OnFrameRecvHandler = [&] (const nghttp2_frame* frame) {
            Callbacks_.OnFrameRecvHandler(descr.Id, frame);
        };
    }

    THolder<THttp2Stream> stream;
    TInstant backendTimeout = http2Backend->GetBackendTimeout().ToDeadLine();

    Y_REQUIRE(descr.Request, yexception{} << "descr.Request == null");
    Y_PROPAGATE_ERROR(connection->StartStream(*descr.Request, *descr.Input, *descr.Output, std::move(callbacks), backendTimeout).AssignTo(stream));
    Y_PROPAGATE_ERROR(stream->Join(backendTimeout));

    return {};
}
