#pragma once

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

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

#include <balancer/kernel/helpers/cast.h>

#include <util/string/cast.h>
#include <util/generic/strbuf.h>
#include <util/generic/maybe.h>

#include <type_traits>


namespace NSrvKernel {

namespace NPrivate {

void TreatConnectionV0(const bool needKeepAlive, const bool explicitConnectionHeader, NSrvKernel::THeaders& headers);
void TreatConnectionV1(const bool upgradeRequested, const bool needKeepAlive, const bool explicitConnectionHeader, NSrvKernel::THeaders& headers);

class THttpEncoder : TMoveOnly {
private:
    class TSlaveOutput : public IIoOutput {
    public:
        TSlaveOutput(IIoOutput* slave) noexcept
            : Slave_(slave)
        {}

    private:
        TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
            return Slave_->Send(std::move(lst), deadline);
        }

    private:
        IIoOutput* const Slave_ = nullptr;
    };

    class TNullSlaveOutput : public IIoOutput {
    public:
        TNullSlaveOutput(IIoOutput* slave) noexcept
            : Slave_(slave)
        {}

    private:
        TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
            if (lst.Empty()) {
                Y_PROPAGATE_ERROR(Slave_->Send(std::move(lst), deadline));
            }
            return {};
        }

    private:
        IIoOutput* const Slave_ = nullptr;
    };

    using TFinishedTag = std::monostate;

public:
    THttpEncoder(IIoOutput* slave) noexcept
        : Slave_(slave)
    {}

    void InitChunkedTransfer(THeaders& headers) noexcept {
        headers.Add("Transfer-Encoding", "chunked");
        Impl_.emplace<TChunkedTransferEncoder>(Slave_);
    }

    void InitContentLength(THeaders& headers, const ui64 contentLength) noexcept {
        headers.Add("Content-Length", ::ToString(contentLength));
        InitPlainContent();
    }

    void InitHeadResponse(THeaders& headers, const std::optional<ui64> contentLength) noexcept {
        if (contentLength) {
            headers.Add("Content-Length", ::ToString(*contentLength));
        }
        InitNullContent();
    }

    void InitPlainContent() noexcept {
        Impl_.emplace<TSlaveOutput>(Slave_);
    }

    void InitNullContent() noexcept {
        Impl_.emplace<TNullSlaveOutput>(Slave_);
    }

    template <class T>
    TError SendMessage(T& message, THeaders* extraHeaders, TMaybe<TChunkList> body, TInstant deadline) {

        TChunksOutputStream s(4196);
        message.BuildTo(s);

        if (extraHeaders) {
            extraHeaders->BuildTo(s);
        }

        s << CRLF;

        if (body) {
            TChunkList chunks = std::move(s.Chunks());
            chunks.Append(std::move(*body));
            Y_PROPAGATE_ERROR(Slave_->Send(std::move(chunks), deadline));
            return Slave_->SendEof(deadline);
        } else {
            return Slave_->Send(std::move(s.Chunks()), deadline);
        }
    }

    bool Finished() const noexcept {
        return Impl_.index() == 0;
    }

    void Finish() noexcept {
        Impl_.emplace<0>();
    }

    TError Send(TChunkList lst, TInstant deadline) noexcept;

private:
    IIoOutput* const Slave_ = nullptr;
    std::variant<TFinishedTag, TSlaveOutput, TChunkedTransferEncoder, TNullSlaveOutput> Impl_;
};

}  // namespace NPrivate

class TToBackendEncoder final : public IIoOutput {
public:
    TToBackendEncoder(IIoOutput* const slave) noexcept
        : Encoder_(slave)
    {}

    TError WriteRequest(const TRequest& request, bool needKeepAlive, TMaybe<TChunkList> body, TInstant deadline) noexcept {
        Y_ASSERT(Encoder_.Finished());
        THeaders extraHeaders;
        Y_PROPAGATE_ERROR(DoEncode(extraHeaders, request.Props(), needKeepAlive));
        Y_PROPAGATE_ERROR(Encoder_.SendMessage(request, &extraHeaders, std::move(body), deadline));
        return {};
        // FIXME(ilezhankin): new possible value of |needKeepAlive| is ignored
    }

    void SwitchProtocols() noexcept {
        Encoder_.InitPlainContent();
    }

private:
    TError DoEncode(THeaders& headers, const TBaseProperties& props, const bool needKeepAlive) noexcept;

    TError DoSend(TChunkList lst, TInstant deadline) noexcept override {
        return Encoder_.Send(std::move(lst), deadline);
    }

private:
    NPrivate::THttpEncoder Encoder_;
};

}  // namespace NSrvKernel
