#pragma once

#include "http2_stream.h"
#include "ping_context.h"
#include "pinger.h"

#include <balancer/kernel/connection_manager_helpers/helpers.h>
#include <balancer/kernel/http/parser/http.h>

#include <nghttp2/nghttp2.h>

#include <util/system/types.h>

namespace NBalancerClient {
class THttp2Connection: public TIntrusiveListItem<THttp2Connection> {
    friend class THttp2Stream;
  public:
    THttp2Connection(THttp2Connection&&) = delete;
    THttp2Connection(const THttp2Connection&) = delete;
    THttp2Connection(const TString& addr, THolder<NModProxy::TKeepAliveData> connection, TPinger& pinger);

    NSrvKernel::TError Start(TInstant deadline);

    NSrvKernel::TErrorOr<THolder<THttp2Stream>> StartStream(const NSrvKernel::TRequest& request,
                                                            NSrvKernel::IIoInput& input,
                                                            NSrvKernel::IHttpOutput& output,
                                                            THttp2Stream::TCallbacks callbacks,
                                                            TInstant deadline);

    const TString& Addr() const;
    TString ResolvedAddr() const;

    bool Alive() const;
    bool HasStreams() const;
    TMaybe<TDuration> IdleTime() const;
  private:
    NSrvKernel::TError CancelStream(THttp2Stream& stream, TInstant deadline);
    NSrvKernel::TError StreamHasNewData(THttp2Stream& stream, bool resume);

    NSrvKernel::TError Send(TInstant deadline);
    void HandleSocket();
    bool HasStream(i32 streamId);
    void OnStreamClose(THttp2Stream* stream, NSrvKernel::TError error);

    static std::shared_ptr<nghttp2_session> CreateSession(THttp2Connection* http2Connection);
    static std::shared_ptr<nghttp2_session_callbacks> CreateCallbacks();
    static ssize_t Read(nghttp2_session *session, int32_t streamId, ui8* buf, size_t length, ui32* dataFlags, nghttp2_data_source* source, void* userData);
    static int OnFrameRecv(nghttp2_session* session, const nghttp2_frame *frame, void *);
    static int OnFrameSent(nghttp2_session* session, const nghttp2_frame *frame, void *);
    static int OnData(nghttp2_session* session, ui8 flags, int32_t streamId, const ui8 *data, size_t len, void *userData);
    static int OnStreamClose(nghttp2_session* session, int32_t streamId, ui32 errorCode, void* userData);
    static int OnHeader(nghttp2_session* session, const nghttp2_frame *frame, nghttp2_rcbuf *nameRcBuf, nghttp2_rcbuf *valueRcBuf, ui8 flags, void *userData);
    static int OnSendData(nghttp2_session* session, nghttp2_frame* frame, const ui8* framehd, size_t length, nghttp2_data_source* source, void* user_data);

    NSrvKernel::TErrorOr<i32> SubmitRequest(const NSrvKernel::TRequest& request);
    NSrvKernel::TError SubmitSettings();

    TString Addr_;
    THolder<NModProxy::TKeepAliveData> BackendConnection_;
    std::shared_ptr<nghttp2_session> Session_;
    TIntrusiveList<THttp2Stream> Streams_;
    bool SendInProgress_ = false;
    TInstant LastStreamTime_ = TInstant::Zero();
    TPinger& Pinger_;
    TPingContext PingContext_;
    NSrvKernel::TError Error_;
    bool DataSent_ = false;
    TContSimpleEvent PrefaceReceivedEvent_;
    NSrvKernel::TChunkList DataToSent_;
    NSrvKernel::TCoroutine Coroutine_;
};
} // namespace NBalancerClient
