#include "http2_conn_in.h"

#include <balancer/kernel/memory/chunks.h>
#include <balancer/kernel/ssl/sslio.h>

namespace NSrvKernel::NHTTP2 {

    TStringBuf GetConnectionPreface() noexcept {
        return HTTP2_PREFACE;
    }

    TConnInput::TConnInput(
        IIoInput& input,
        TLogger& logger,
        TStats& stats,
        const TServerSettings& serverSettings
    ) noexcept
        : Logger_(logger)
        , Stats_(stats)
        , ServerSettings_(serverSettings)
        , Input_(&input)
    {}


    TErrorOr<bool> TConnInput::RecvPreface() noexcept {
        Y_HTTP2_FUNC_E(Logger_);

        TChunkList lst;
        bool peeked = false;

        Y_TRY(TError, error) {
            Y_PROPAGATE_ERROR(Peek().AssignTo(peeked));
            if (!peeked) {
                Stats_.BeforePreface.OnClientFin();
                Y_HTTP2_EVENT_E(Logger_, "Client FIN before PRI");
                return TError{};
            }

            return Input_.Read(lst, GetConnectionPreface().size());
        } Y_CATCH {
            if (const auto* sysErr = error.GetAs<TSystemError>()) {
                Y_HTTP2_CATCH(Logger_, *error, sysErr->Status());
                if (Stats_.BeforePreface.OnError(ErrnoToIOError(sysErr->Status()))) {
                    return false;
                }
            } else if (const auto* sslErr = error.GetAs<TSslError>()) {
                Y_HTTP2_CATCH(Logger_, *error, sslErr->Status());
                Stats_.BeforePreface.OnError(EIOError::SSL);
            } else {
                Y_HTTP2_CATCH_E(Logger_, *error);
            }
            return error;
        }
        if (peeked) {
            Y_REQUIRE(GetConnectionPreface() == lst,
                TConnectionError(EErrorCode::PROTOCOL_ERROR, EConnProtocolError::InvalidPreface)
                    << "Unexpected preface string: " << EscC(lst))
            return true;
        }
        return false;
    }

    TErrorOr<TMaybe<TFrame>> TConnInput::RecvFrame() noexcept {
        Y_HTTP2_FUNC_E(Logger_);

        bool peeked;
        Y_PROPAGATE_ERROR(Peek().AssignTo(peeked));
        if (!peeked) {
            return Nothing();
        }

        TFrame frame;

        {
            TStackRegion<RFC_FRAME_HEADING_SIZE, TInputRegion> headingBuf;

            Y_PROPAGATE_ERROR(Input_.Read(headingBuf.Data(), headingBuf.Size()));
            frame.Heading = TFrameHeading::Parse(headingBuf.GetRegion().AsStringBuf());

            Y_PROPAGATE_ERROR(ValidateFrameHeading(frame.Heading));

            if (frame.Heading.Length) {
                THolder<TChunkData> chunkDataPtr = NewChunkData(frame.Heading.Length);

                Y_PROPAGATE_ERROR(Input_.Read(chunkDataPtr->Data(), frame.Heading.Length));
                frame.Payload = NewChunk(frame.Heading.Length, std::move(chunkDataPtr));
            } else {
                frame.Payload = EmptyChunk();
            }
        }

        return TMaybe<TFrame>{ std::move(frame) };
    }

    TErrorOr<bool> TConnInput::Peek() noexcept {
        Y_HTTP2_FUNC_E(Logger_);
        return Input_.Peek();
    }

    TError TConnInput::ValidateFrameHeading(const TFrameHeading& heading) const noexcept {
        Y_HTTP2_FUNC(Logger_, heading);

        Y_REQUIRE(heading.Length <= ServerSettings_.GetCurrent().MaxFrameSize,
            TConnectionError(EErrorCode::FRAME_SIZE_ERROR, TUnspecifiedReason()));

        switch (heading.Type) {
        case EFrameType::DATA:
            return ValidateDataHeading(heading, ServerSettings_.GetCurrent());
        case EFrameType::HEADERS:
            return ValidateHeadersHeading(heading, ServerSettings_.GetCurrent());
        case EFrameType::PRIORITY:
            return TPriority::ValidateHeading(heading);
        case EFrameType::RST_STREAM:
            return ValidateRstStreamHeading(heading);
        case EFrameType::SETTINGS:
            return TClientSettings::Validate(heading, ServerSettings_.GetCurrent());
        case EFrameType::PING:
            return TPing::Validate(heading);
        case EFrameType::GOAWAY:
            return TGoAway::Validate(heading, ServerSettings_.GetCurrent());
        case EFrameType::WINDOW_UPDATE:
            return ValidateWindowUpdateHeading(heading);
        default:
            return {};
        }
    }
}
