#pragma once

#include "headers.h"
#include "http.h"
#include "parse_options.h"

#include <balancer/kernel/custom_io/chunkedtransferio.h>
#include <balancer/kernel/custom_io/chunkio.h>
#include <balancer/kernel/custom_io/limitio.h>
#include <balancer/kernel/custom_io/null.h>
#include <balancer/kernel/io/iobase.h>
#include <balancer/kernel/memory/chunks.h>

#include <util/generic/ptr.h>
#include <util/generic/strbuf.h>

#include <type_traits>


namespace NSrvKernel {

namespace NPrivate {

class THttpDecoder : TMoveOnly {
private:
    class TSlaveInput : public IIoInput {
    public:
        TSlaveInput(IIoInput* slave) noexcept
            : Slave_(slave)
        {}

    private:
        TError DoRecv(TChunkList& lst, TInstant deadline) noexcept override {
            return Slave_->Recv(lst, deadline);
        }

    private:
        IIoInput* const Slave_ = nullptr;
    };

public:
    THttpDecoder(IIoInput* slave) noexcept
        : Slave_(slave)
    {}

    template <class T>
    TError ReadMessage(T& message, const THttpParseOptions& options, TInstant deadline) noexcept {
        TChunkList unparsed;

        Y_PROPAGATE_ERROR(message.Read(Slave_, unparsed, options, deadline));
        Slave_->UnRecv(std::move(unparsed));

        return {};
    }

    void InitSlaveInput() noexcept {
        Impl_.emplace<TSlaveInput>(Slave_);
    }

    void InitChunkedInput() noexcept {
        Impl_.emplace<TChunkedTransferDecoder>(Slave_);
    }

    void InitLengthEqualInput(ui64 length) noexcept {
        Impl_.emplace<TLengthEqualInput>(Slave_, length);
    }

    void InitNullInput() noexcept {
        Impl_.emplace<TNullStream>();
    }

    TError Recv(TChunkList& lst, TInstant deadline) noexcept;

private:
    IIoInput* const Slave_ = nullptr;
    std::variant<std::monostate, TSlaveInput, TChunkedTransferDecoder,
                 TLengthEqualInput, TNullStream> Impl_;
};

}  // namespace NPrivate

class TFromClientDecoder final : public IIoInput {
public:
    TFromClientDecoder(IIoInput* slave, THttpParseOptions options = {}) noexcept
        : Decoder_(slave)
        , Options_(options)
    {}

    TError ReadRequest(TRequest& request, TInstant deadline) noexcept {
        Y_PROPAGATE_ERROR(Decoder_.ReadMessage(request, Options_, deadline));
        Init(request);
        return {};
    }

    // TODO(tender-bum): move Init to private
    void Init(const TRequest& request) noexcept;

    void ForceNullInput() noexcept {
        Decoder_.InitNullInput();
    }

    void SwitchProtocols() noexcept {
        Decoder_.InitSlaveInput();
    }

    EStreamState StreamState() noexcept {
        if (StreamState_ == EStreamState::Open) {
            TryUpdateStreamState();
        }
        return StreamState_;
    }
private:
    TError DoRecv(TChunkList& lst, TInstant deadline) noexcept override {
        TError error = Decoder_.Recv(lst, deadline);

        if (error) {
            StreamState_ = EStreamState::Error;
        } else if (lst.Empty()) {
            StreamState_ = EStreamState::Close;
        }

        return error;
    }

    void TryUpdateStreamState() {
        TryPeek(Now());
    }
private:
    NPrivate::THttpDecoder Decoder_;
    THttpParseOptions Options_;
    EStreamState StreamState_ = EStreamState::Open;
};

class TFromBackendDecoder final : public IIoInput {
public:
    TFromBackendDecoder(IIoInput* slave, bool headRequest = false, THttpParseOptions options = {}) noexcept
        : Decoder_(slave)
        , HeadRequest_(headRequest)
        , ParseOptions_(std::move(options))
    {}

    TFromBackendDecoder(IIoInput* slave, const TRequest& request, THttpParseOptions options = {}) noexcept
        : TFromBackendDecoder(slave, EMethod::HEAD == request.RequestLine().Method, std::move(options))
    {}

    TError ReadResponse(TResponse& message, TInstant deadline) noexcept {
        Y_PROPAGATE_ERROR(Decoder_.ReadMessage(message, ParseOptions_, deadline));
        Init(message);
        return {};
    }

    // TODO(tender-bum): move Init to private
    void Init(const TResponse& message) noexcept;

    void SwitchProtocols() noexcept {
        Decoder_.InitSlaveInput();
    }

private:
    TError DoRecv(TChunkList& lst, TInstant deadline) noexcept override {
        return Decoder_.Recv(lst, deadline);
    }

private:
    NPrivate::THttpDecoder Decoder_;
    bool HeadRequest_ = false;
    THttpParseOptions ParseOptions_;
};

}  // namespace NSrvKernel
