#include "http2.h"
#include "http2_conn.h"

#include <balancer/kernel/http2/server/utils/http2_log.h>

#include <balancer/kernel/module/iface.h>

#include <util/generic/strbuf.h>

namespace NSrvKernel::NHTTP2 {

    TSettings GetDefaultServerSettings() noexcept {
        TSettings settings;
        settings.MaxHeaderListSize = IMPL_MAX_HEADER_LIST_SIZE_DEFAULT;
        settings.HeaderTableSize = IMPL_HEADER_TABLE_SIZE_DEFAULT;
        settings.MaxConcurrentStreams = IMPL_MAX_CONCURRENT_STREAMS_DEFAULT;
        settings.InitialWindowSize = IMPL_INITIAL_WINDOW_SIZE_DEFAULT;
        return settings;
    }


    IHTTP2Module::IHTTP2Module() noexcept {
        TConnection::InitStatic();
    }

    namespace {
        size_t PeekChunksTo(IIoInput& source, void* buf, size_t len) noexcept {
            auto chunks = source.RecvBuffered();
            size_t copied = chunks.CopyDataTo(buf, len);
            source.UnRecv(std::move(chunks));
            return copied;
        }

        TErrorOr<bool> InspectForHttp2Preface(const TConnDescr& descr) noexcept {
            size_t recvBytes = 0;
            constexpr size_t priSize = 4;
            Y_PROPAGATE_ERROR(descr.Input->FillBuffer(priSize, TInstant::Max()).AssignTo(recvBytes));
            if (recvBytes < HTTP2_PREFACE.size() && recvBytes >= priSize) {
                char pri[priSize];
                PeekChunksTo(*descr.Input, pri, priSize);
                if (TStringBuf{pri, priSize} == HTTP2_PREFACE.SubStr(0, priSize)) {
                    Y_PROPAGATE_ERROR(descr.Input->FillBuffer(HTTP2_PREFACE.size(), TInstant::Max()).AssignTo(recvBytes));
                }
            }
            if (recvBytes >= HTTP2_PREFACE.size()) {
                char prefaceBuf[HTTP2_PREFACE.size()];
                PeekChunksTo(*descr.Input, prefaceBuf, HTTP2_PREFACE.size());
                return TStringBuf{prefaceBuf, HTTP2_PREFACE.size()} == HTTP2_PREFACE;
            }
            return false;
        }
    }

    TError IHTTP2Module::OnConnection(
        const TConnDescr& descr,
        TLog* debugLog,
        const TSettings& serverSettings,
        const TAuxServerSettings& auxServerSettings
    ) const {
        TStats& stats = GetStats(descr);
        TLogLabel logLabel{GetModuleName(), descr};

        THolder<TLog> multiLog;
        TLog* log = nullptr;

        if (descr.ErrorLog && debugLog) {
            multiLog.Reset(new TLog(MakeHolder<TBilogBackend>(descr.ErrorLog, debugLog)));
            multiLog->SetDefaultPriority(multiLog->FiltrationLevel());
            log = multiLog.Get();
        } else if (descr.ErrorLog) {
            log = descr.ErrorLog;
        } else {
            log = debugLog;
        }

        TLogger logger(log, logLabel, descr.Process().Executor());

        bool http2 = false;
        if (descr.Ssl().ThisConnIsSsl) {
            http2 = EClientProto::CP_HTTP2 == descr.Ssl().NextProto;
        } else if (auxServerSettings.AllowHttp2WithoutSsl) {
            Y_PROPAGATE_ERROR(InspectForHttp2Preface(descr).AssignTo(http2));
        }

        if (http2) {
            Y_HTTP2_BLOCK_E(logger, "http/2 session");
            stats.H2Conn.OnOpen();

            Y_TRY(TError, err) {
                Y_PROPAGATE_ERROR(
                    TConnection(
                        *this,
                        descr,
                        logger,
                        serverSettings,
                        auxServerSettings
                    ).RunConnection()
                );
                stats.H2Conn.OnClose();
                return {};
            } Y_CATCH {
                Y_HTTP2_CATCH_E(logger, *err);
                stats.H2Conn.OnAbort();
                return err;
            }
        } else {
            Y_HTTP2_BLOCK_E(logger, "http/1.x session");
            stats.H1Conn.OnOpen();

            Y_TRY(TError, err) {
                Y_PROPAGATE_ERROR(OnHTTP(descr));
                stats.H1Conn.OnClose();
                return {};
            } Y_CATCH {
                Y_HTTP2_CATCH_E(logger, *err);
                stats.H1Conn.OnAbort();
                return err;
            }
        }

        return {};
    }

    TError IHTTP2Module::OnHTTP(const TConnDescr& descr) const {
        return GetNextModule().Run(descr);
    }
}
