#include "http2_stream_in.h"
#include "http2_conn_out.h"
#include "http2_stats.h"

namespace NSrvKernel::NHTTP2 {

    TStreamInput::TStreamInput(TStreamId streamId, const TBaseProperties& reqProps, IConnection& conn) noexcept
        : TStreamId(streamId)
        , Logger_(conn.GetLogger())
        , Stats_(conn.GetStats())
        , Output_(conn.GetClientOutput())
        , Flow_(Logger_, conn.GetServerSettings())
        , Executor_(conn.GetExecutor())
    {
        if (reqProps.ContentLength) {
            ContentLength_ = *reqProps.ContentLength;
        }
    }

    void TStreamInput::PrintTo(IOutputStream& out) const {
        Y_HTTP2_PRINT_OBJ(out, StreamId_, State_, Flow_, ContentLength_, RecvLength_, Buffer_.size());
    }

    TError TStreamInput::OnData(TFrame frame) noexcept {
        Y_HTTP2_METH(Logger_, frame.Heading);

        Y_REQUIRE(IsOpen(),
            TConnectionError(EErrorCode::INTERNAL_ERROR, EConnInternalError::UnexpectedData));

        {
            const auto length = frame.Heading.Length;
            Y_PROPAGATE_ERROR(Flow_.Consume(length));
            WindowUpdate_ += length;
        }

        Y_PROPAGATE_ERROR(StripPadding(frame));

        if (frame.Heading.Length) {
            RecvLength_ += frame.Heading.Length;

            // This check is superimportant, it prevents request smuggling attacks via http/2. Do not remove.
            // https://portswigger.net/web-security/request-smuggling
            Y_REQUIRE(!ContentLength_ || *ContentLength_ >= RecvLength_,
                TStreamError(EErrorCode::PROTOCOL_ERROR, EStreamProtocolError::ContentLengthExceeded)
                          << " content-length mismatch: " << ContentLength_ << " < " << RecvLength_);

            Buffer_.Push(std::move(frame.Payload));
            EmptyBufferCV_.notify();
        }

        if (frame.Heading.HasFlagEndStream()) {
            Y_PROPAGATE_ERROR(Close());
        }

        return {};
    }

    TError TStreamInput::Close() noexcept {
        Y_HTTP2_METH_E(Logger_);

        Y_REQUIRE(IsOpen(),
            TConnectionError(EErrorCode::INTERNAL_ERROR, EConnInternalError::UnexpectedClose));

        Stats_.Stream.OnClientClose();

        State_ = EState::Closed;
        EmptyBufferCV_.notify();

        Y_REQUIRE(!ContentLength_ || *ContentLength_ == RecvLength_,
            TStreamError(EErrorCode::PROTOCOL_ERROR, EStreamProtocolError::ContentLengthNotMatched)
                << " content-length mismatch: " << ContentLength_ << " != " << RecvLength_);
        return {};
    }

    void TStreamInput::Cancel() noexcept {
        Y_HTTP2_METH_E(Logger_);
        State_ = EState::Cancelled;
        EmptyBufferCV_.notify();
    }

    TErrorOr<TChunkList> TStreamInput::DequeueData(TInstant deadline) noexcept {
        Y_HTTP2_METH_E(Logger_);

        while (IsOpen() && Buffer_.Empty()) {
            const int ret = EmptyBufferCV_.wait_until(&Executor_, deadline);;
            Y_REQUIRE(ret == EWAKEDUP,
                      TSystemError{ret});
        }

        if (WindowUpdate_ && IsOpen()) {
            Y_PROPAGATE_ERROR(Flow_.Update(WindowUpdate_));
            Y_PROPAGATE_ERROR(Output_.SendStreamWindowUpdate(GetStreamId(), WindowUpdate_));
            WindowUpdate_ = 0;
        }

        return std::move(Buffer_);
    }
}

Y_HTTP2_GEN_PRINT(TStreamInput);
