#include "deferred_messages_queue.h"

#include <saas/indexerproxy/configs/config.h>
#include <saas/protos/rtyserver.pb.h>

#include <kernel/multipart_archive/queue/queue_storage.h>

#include <util/string/hex.h>
#include <util/stream/file.h>

class TLocalMultipartQueue : public TDeferredMessagesQueue::IQueue {
public:
    TLocalMultipartQueue(const TDispatcherConfig& config, const TString& name, const TDeferredMessagesQueue::TQueueHeader& header)
        : Config(config)
        , Name(name)
        , Header(header)
        , Storage(Config.GetDeferredMQConfig().MultipartConfig.Directory / Name / "queue",
            NRTYArchive::TStorage::TLimits(
                Config.GetDeferredMQConfig().QueueSizeLimit,
                Config.GetDeferredMQConfig().MultipartConfig.SizeInBytesLimit,
                Config.GetOwner().GetServicesConfig().GetConfig(header.Service).MaxDocsInWorkPerBackend
            ),
            Config.GetDeferredMQConfig().MultipartConfig.ArchiveConfig
        )
    {
        TFsPath hdrPath = Config.GetDeferredMQConfig().MultipartConfig.Directory / Name / "queue.hdr";
        if (hdrPath.Exists()) {
            const auto& oldHeader = FromString<TDeferredMessagesQueue::TQueueHeader>(TUnbufferedFileInput(hdrPath).ReadAll());
            if (oldHeader != Header) {
                WARNING_LOG << "Clearing queue " << Name << " as its header has changed: " << oldHeader << " -> " << Header << Endl;
                Storage.Clear();
            }
        }
        TFileOutput hdr(hdrPath);
        hdr.SetFinishPropagateMode(true);
        hdr.Write(ToString(Header));
    }

    IMessage::TPtr Get() override {
        NRTYArchive::TStorage::TDocument::TPtr doc = Storage.Get(false);
        if (!doc)
            return nullptr;
        THolder<TMessage> result(new TMessage(doc));
        if (!result->Parse()) {
            result->Ack();
            result.Reset(nullptr);
            if (!Config.GetLog().IsNullLog()) {
                const TString& dump = HexEncode(TString((const char*)doc->GetBlob().AsCharPtr(), doc->GetBlob().Size()));
                Config.GetLog() << NLoggingImpl::GetLocalTimeS() << '\t' << Name << '\t' << "PARSE_FAILED" << '\t' << dump << Endl;
            }
        }
        return result.Release();
    }

    TDeferredMessagesQueue::TQueueHeader GetHeader() const override {
        return Header;
    }

    bool CheckMessageForStore(const NRTYServer::TMessage& /*message*/, TString& /*reply*/) const override {
        return true;
    }

    void Store(const NRTYServer::TMessage& data) override {
        TBuffer buffer;
        buffer.Resize(data.ByteSize());
        Y_PROTOBUF_SUPPRESS_NODISCARD data.SerializeToArray(buffer.data(), buffer.size());
        Storage.Put(TBlob::FromBuffer(buffer));
    }

    void Purge() override {
        Storage.Clear();
    }

    void CollectGarbage() override {
    }

    ui64 SizeInBytes() const override {
        return Storage.GetSizeInBytes();
    }

    ui32 Size() const override {
        return Storage.GetDocsCount();
    }

    void Stop() override {
        Storage.Stop();
    }

    static TFactory::TRegistrator<TLocalMultipartQueue> Registrator;

private:
    class TMessage : public IQueue::IMessage {
    public:
        TMessage(NRTYArchive::TStorage::TDocument::TPtr doc)
            : Doc(doc)
        {}

        const NRTYServer::TMessage& Data() const override {
            return Message;
        }

        const TBlob& RawData() const override {
            return Doc->GetBlob();
        }

        void Ack() override {
            Doc->Ack();
        }

        bool Parse() {
            return Message.ParseFromArray(Doc->GetBlob().AsCharPtr(), Doc->GetBlob().Size());
        }

    private:
        NRTYArchive::TStorage::TDocument::TPtr Doc;
        NRTYServer::TMessage Message;
    };

private:
    const TDispatcherConfig& Config;
    const TString Name;
    const TDeferredMessagesQueue::TQueueHeader Header;
    NRTYArchive::TStorage Storage;
};

TLocalMultipartQueue::TFactory::TRegistrator<TLocalMultipartQueue> TLocalMultipartQueue::Registrator(TDeferredMQConfig::LOCAL_MULTIPART);
