#include "processors.h"

#include <saas/protos/rtyserver.pb.h>
#include <saas/rtyserver/components/suggest/config/config.h>
#include <saas/rtyserver/components/suggest/html_parser/parser_helpers.h>
#include <saas/rtyserver/config/config.h>
#include <saas/rtyserver/config/const.h>

#include <robot/favicon/protos/tables.pb.h>
#include <robot/gemini/protos/base.pb.h>

#include <library/cpp/json/json_reader.h>
#include <saas/library/erasure/erasure.h>

#include <util/generic/string.h>
#include <util/generic/yexception.h>
#include <util/string/builder.h>
#include <util/string/join.h>
#include <util/string/split.h>
#include <util/string/vector.h>

namespace NSaas {
    // ====================== IRowProcessor ==========================
    IRowProcessor::IRowProcessor(const TRowProcessorOptions& options)
        : Options(options)
    {
    }

    TVector<TAction> IRowProcessor::ProcessRow(const NYT::TNode& row) {
        DoValidateRow(row);
        return DoProcessRow(row);
    }

    TAction IRowProcessor::ProcessRowSingle(const NYT::TNode& row) {
        DoValidateRow(row);
        return DoProcessRowSingle(row);
    }

    TAction IRowProcessor::DoProcessRowSingle(const NYT::TNode& row) {
        // blank implementation
        Y_UNUSED(row);
        return TAction();
    }

    TVector<TAction> IRowProcessor::DoProcessRow(const NYT::TNode& row) {
        TVector<TAction> result;
        result.push_back(DoProcessRowSingle(row));
        return result;
    }

    TVector<TAction> IRowProcessor::ProcessRow(const NMR::TValue& key, const NMR::TValue& subkey, const NMR::TValue& value) {
        return ProcessRow(NYT::TNode()("key", key.AsString())("subkey", subkey.AsString())("value", value.AsString()));
    }

    TAction IRowProcessor::ProcessRowSingle(const NMR::TValue& key, const NMR::TValue& subkey, const NMR::TValue& value) {
        return ProcessRowSingle(NYT::TNode()("key", key.AsString())("subkey", subkey.AsString())("value", value.AsString()));
    }

    bool IRowProcessor::ShouldSkipRow(const NYT::TNode& /*row*/) const {
        return false;
    }

    bool IRowProcessor::NeedConfigFiles() const {
        return false;
    }

    // ====================== ActionProcessor ========================

    class IActionProcessor;

    class IActionProcessor {
    public:
        using TFactory = NObjectFactory::TParametrizedObjectFactory<IActionProcessor, TString, IActionProcessor*>;
    public:
        IActionProcessor(IActionProcessor *processor) {
            InternalProcessor.Reset(processor);
        }

        virtual ~IActionProcessor() = default;

        TVector<TAction> ProcessActions(TVector<TAction> actions) {
            if (InternalProcessor.Get()) {
                actions = InternalProcessor->ProcessActions(std::move(actions)); // calling nested splitter
            }

            return DoProcessActions(std::move(actions));
        }

        virtual TVector<TAction> DoProcessActions(TVector<TAction> actions) = 0;

    private:
        THolder<IActionProcessor> InternalProcessor = nullptr; // Helps to be stackable
    };

    // ======================= Erasure ========================

    template <ui32 MAIN_NUMBER, ui32 PARITY_NUMBER>
    class TSimpleErasureActionProcessor : public IActionProcessor {
    public:
        using IActionProcessor::IActionProcessor;

        struct TErasureInfo {
            ui32 MainNumber = 3;
            ui32 ParityNumber = 2;
        };
    public:
        TSimpleErasureActionProcessor(IActionProcessor *processor) : IActionProcessor(processor), ErasureInfo{MAIN_NUMBER, PARITY_NUMBER} {}

        TVector<TAction> DoProcessActions(TVector<TAction> actions) override;

    private:
        TVector<TAction> ProcessAction(TAction action);

    private:
        TErasureInfo ErasureInfo;
        using TRegistrator = IActionProcessor::TFactory::TRegistrator<TSimpleErasureActionProcessor>;
        static TRegistrator Registrator;
    };

    template<>
    TSimpleErasureActionProcessor<3, 2>::TRegistrator TSimpleErasureActionProcessor<3, 2>::Registrator("erasure03+02");

    template <ui32 MAIN_NUMBER, ui32 PARITY_NUMBER>
    TVector<TAction> TSimpleErasureActionProcessor<MAIN_NUMBER, PARITY_NUMBER>::DoProcessActions(TVector<TAction> actions) {
        TVector<TAction> result;

        for (auto&& action : actions) {
            for (auto&& newAction : ProcessAction(std::move(action))) {
                result.push_back(std::move(newAction));
            }
        }

        return result;
    }

    template<ui32 MAIN_NUMBER, ui32 PARITY_NUMBER>
    TVector<TAction> TSimpleErasureActionProcessor<MAIN_NUMBER, PARITY_NUMBER>::ProcessAction(TAction action) {
        TVector<TAction> result;
        TString serialization;
        Y_PROTOBUF_SUPPRESS_NODISCARD action.ToProtobuf().SerializeToString(&serialization);

        NSaas::TErasureHelper erasure;

        auto parts = erasure.SplitToParts(serialization);

        for (auto &&part : parts) {
            NRTYServer::TMessage message;
            auto document = message.MutableDocument();
            TString body;
            Y_PROTOBUF_SUPPRESS_NODISCARD part.SerializeToString(&body);
            document->SetBody(std::move(body));
            result.emplace_back(std::move(message));
        }

        return result;
    }

    // =============== Decorator =================

    class TRowProcessorDecorator : public IRowProcessor {
    public:
        TRowProcessorDecorator(const TRowProcessorOptions& options, IRowProcessor *rowProcessor, IActionProcessor *actionProcessor);
        virtual ~TRowProcessorDecorator() = default;
    protected:
        void DoValidateRow(const NYT::TNode& row) override;
        TVector<TAction> DoProcessRow(const NYT::TNode& row) override;
    private:
        THolder<IRowProcessor> RowProcessor;
        THolder<IActionProcessor> ActionProcessor;
    };

    TRowProcessorDecorator::TRowProcessorDecorator(const TRowProcessorOptions& options, IRowProcessor *rowProcessor, IActionProcessor *actionProcessor) : IRowProcessor(options) {
        Y_ENSURE(rowProcessor, "Can't construct row processor split decorator with null row processor");
        Y_ENSURE(actionProcessor, "Can't construct row processor split decorator with null action processor");
        RowProcessor.Reset(rowProcessor);
        ActionProcessor.Reset(actionProcessor);
    }

    void TRowProcessorDecorator::DoValidateRow(const NYT::TNode& row) {
        RowProcessor->DoValidateRow(row);
    }

    TVector<TAction> TRowProcessorDecorator::DoProcessRow(const NYT::TNode& row) {
        auto actions = RowProcessor->DoProcessRow(row);
        return ActionProcessor->ProcessActions(std::move(actions));
    }

    // ============== TRowDecoratorFactory ===========

    IRowProcessor* TRowDecoratorFactory::Construct(const TString& key, const TRowProcessorOptions& options) {
        TVector<TString> subkeys = StringSplitter(key).Split('_');
        Y_ENSURE(!subkeys.empty());
        auto rowProcessor = IRowProcessor::TFactory::Construct(subkeys[0], options);

        if (subkeys.size() == 1) {
            return rowProcessor;
        } else {
            IActionProcessor* actionProcessor = nullptr;

            for (ui32 i = subkeys.size() - 1; i > 0; --i) {
                actionProcessor = IActionProcessor::TFactory::Construct(subkeys[i], actionProcessor); // making the chain
            }

            return new TRowProcessorDecorator(options, rowProcessor, actionProcessor);
        }
    }

    // ============== IDataRowProcessor ==============

    TAction IDataRowProcessor::DoProcessRowSingle(const NYT::TNode& row) {
        TAction action = CreateDefaultAction();
        DoFillKps(action, row);
        DoFillUrl(action.GetDocument(), row);

        if (action.GetActionType() != NSaas::TAction::atDelete) {
            DoFillBody(action.GetDocument(), row);
        }

        return action;
    }

    TAction IDataRowProcessor::CreateDefaultAction() {
        TAction action;
        action.SetActionType(Options.ActionType);
        if (Options.Prefix != 0) {
            action.SetPrefix(Options.Prefix);
        }

        auto& doc = action.AddDocument();
        if (Options.Timestamp != Max<ui64>()) {
            doc.SetTimestamp(Options.Timestamp);
        }
        if (Options.DeadlineMinutesUTC != 0) {
            doc.SetDeadlineMinutesUTC(Options.DeadlineMinutesUTC);
        }

        return action;
    }

    void IDataRowProcessor::AddProperty(NSaas::TDocument& doc, const TString& key, const TString& value) {
        if (Options.Compression != NMetaProtocol::CM_COMPRESSION_NONE) {
            doc.AddBinaryProperty(::ToString(Options.Compression) + "_" + key, NMetaProtocol::GetDefaultCompressor().Compress(value, Options.Compression));
        } else {
            doc.AddAttribute(key).AddValue(value).AddType(NSaas::TAttributeValue::avtProp);
        }
    }

    void IDataRowProcessor::AddProperty(NSaas::TDocument& doc, const TString& keyEqualsValue) {
        TVector<TString> data;
        StringSplitter(keyEqualsValue).Split('=').SkipEmpty().Limit(2).Collect(&data);
        Y_ENSURE(data.size() == 2, "invalid property line '" << keyEqualsValue << "', expected key=value format");
        AddProperty(doc, data[0], data[1]);
    }

    void IDataRowProcessor::AddTag(NSaas::TDocument& doc, const TString& key, const TString& value) {
        doc.AddAttribute(key).AddValue(value).AddType(NSaas::TAttributeValue::avtSpecKey);
    }

    class TSimpleRowProcessor : public IDataRowProcessor {
        using IDataRowProcessor::IDataRowProcessor;

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(!row["key"].AsString().empty());
        }

        void DoFillKps(TAction& action, const NYT::TNode& row) override {
            if (row.HasKey("subkey")) {
                ui64 kps = FromString<ui64>(row["subkey"].AsString());
                if (kps != 0) {
                    action.SetPrefix(kps);
                }
            }
        }

        void DoFillUrl(TDocument& doc, const NYT::TNode& row) override {
            doc.SetUrl(row["key"].AsString());
        }

        void DoFillBody(TDocument& doc, const NYT::TNode& row) override {
            doc.SetBody(row["value"].AsString());
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TSimpleRowProcessor>;
        static TRegistrator Registrator;
    };

    TSimpleRowProcessor::TRegistrator TSimpleRowProcessor::Registrator("simple");

    class TPersonalRowProcessor : public IDataRowProcessor {
        using IDataRowProcessor::IDataRowProcessor;

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(!row["key"].AsString().empty());
            Y_ENSURE(!row["value"].AsString().empty());
        }

        void DoFillKps(TAction& /*action*/, const NYT::TNode& /*row*/) override {
        }

        void DoFillUrl(TDocument& doc, const NYT::TNode& row) override {
            doc.SetUrl(row["key"].AsString());
        }

        void DoFillBody(TDocument& doc, const NYT::TNode& row) override {
            AddProperty(doc, row["value"].AsString());
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TPersonalRowProcessor>;
        static TRegistrator Registrator;
    };

    TPersonalRowProcessor::TRegistrator TPersonalRowProcessor::Registrator("personal");

    class TGeminiRowProcessor : public IDataRowProcessor {
        using IDataRowProcessor::IDataRowProcessor;

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(!row["Host"].AsString().empty());
            Y_ENSURE(!row["Path"].AsString().empty());
            Y_ENSURE(!row["MainHost"].AsString().empty());
            Y_ENSURE(!row["MainPath"].AsString().empty());
            Y_ENSURE(!row["BeautyUrl"].AsString().empty());
            Y_ENSURE(!row["IndexType"].AsString().empty());
        }

        void DoFillKps(TAction& action, const NYT::TNode& row) override {
            NGeminiProtos::EIndexType index;
            if (NGeminiProtos::EIndexType_Parse(row["IndexType"].AsString(), &index)) {
                Y_ENSURE(index != NGeminiProtos::EIndexType::UNKNOWN_INDEX);
                action.SetPrefix(index);
            }
        }

        void DoFillUrl(TDocument& doc, const NYT::TNode& row) override {
            const TString& url = row["Host"].AsString() + row["Path"].AsString();
            doc.SetUrl(url);
        }

        void DoFillBody(TDocument& doc, const NYT::TNode& row) override {
            const TString& mainUrl = row["MainHost"].AsString() + row["MainPath"].AsString();
            if (doc.GetUrl() == mainUrl) {
                AddProperty(doc, "IsMain", "1");
                AddProperty(doc, "Main", "");
            } else {
                AddProperty(doc, "IsMain", "0");
                AddProperty(doc, "Main", mainUrl);
            }
            if (doc.GetUrl() == row["BeautyUrl"].AsString()) {
                AddProperty(doc, "IsBeauty", "1");
                AddProperty(doc, "Beauty", "");
            } else {
                AddProperty(doc, "IsBeauty", "0");
                AddProperty(doc, "Beauty", row["BeautyUrl"].AsString());
            }
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TGeminiRowProcessor>;
        static TRegistrator Registrator;
    };

    TGeminiRowProcessor::TRegistrator TGeminiRowProcessor::Registrator("gemini");

    class TCardsGeoRowProcessor : public IDataRowProcessor {
    public:
        TCardsGeoRowProcessor(const TRowProcessorOptions& options)
            : IDataRowProcessor(options)
            , UrlPrefix(Options.OtherOptions)
        {
            Y_ENSURE(UrlPrefix, "Can't initialize processor without special url prefix");
        }

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(!row["key"].AsString().empty());
            Y_ENSURE(row["value"].IsString());
        }

        void DoFillKps(TAction& /*action*/, const NYT::TNode& /*row*/) override {
        }

        void DoFillUrl(TDocument& doc, const NYT::TNode& row) override {
            doc.SetUrl(row["key"].AsString() + UrlPrefix);
        }

        void DoFillBody(TDocument& doc, const NYT::TNode& row) override {
            AddProperty(doc, row["value"].AsString());
            AddTag(doc, "merge_key", row["key"].AsString());
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TCardsGeoRowProcessor>;
        static TRegistrator Registrator;

        const TString& UrlPrefix;
    };

    TCardsGeoRowProcessor::TRegistrator TCardsGeoRowProcessor::Registrator("cards_geo");

    class TPersonalGeoRowProcessor : public IDataRowProcessor {
        using IDataRowProcessor::IDataRowProcessor;

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(!row["key"].AsString().empty());
            Y_ENSURE(row["subkey"].IsString());
            Y_ENSURE(row["value"].IsString());
        }

        void DoFillKps(TAction& /*action*/, const NYT::TNode& /*row*/) override {
        }

        void DoFillUrl(TDocument& doc, const NYT::TNode& row) override {
            doc.SetUrl(row["key"].AsString());
        }

        void DoFillBody(TDocument& doc, const NYT::TNode& row) override {
            TVector<TString> ids;
            Split(row["subkey"].AsString(), ",", ids);
            for (auto&& id : ids) {
                AddTag(doc, "merge_key", id);
            }
            const TString& value = row["value"].AsString();
            if (value.StartsWith("tile_pois=") || value.StartsWith("bad_tile_pois=") ||
                value.StartsWith("data=") || value.StartsWith("zoom_tile_pois="))
            {
                AddProperty(doc, value);
            } else {
                AddProperty(doc, "data", value);
            }
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TPersonalGeoRowProcessor>;
        static TRegistrator Registrator;
    };

    TPersonalGeoRowProcessor::TRegistrator TPersonalGeoRowProcessor::Registrator("personal_geo");

    class TGeoYTRowProcessor : public IDataRowProcessor {
    public:
        TGeoYTRowProcessor(const TRowProcessorOptions& options)
            : IDataRowProcessor(options)
            , KeyColumn(options.OtherOptions.empty() ? "doc_url" : options.OtherOptions)
        {
        }

        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(row[KeyColumn].AsString());
        }

        void DoFillKps(TAction& /*action*/, const NYT::TNode& /*row*/) override {
        }

        void DoFillUrl(TDocument& doc, const NYT::TNode& row) override {
            doc.SetUrl(row[KeyColumn].AsString());
        }

        void DoFillBody(TDocument& doc, const NYT::TNode& row) override {
            for (auto&& cell : row.AsMap()) {
                if (cell.first == KeyColumn) {
                    continue;
                }
                const NYT::TNode& val = cell.second;
                TString valStr;
                if (val.IsString()) {
                    valStr = val.AsString();
                } else if (val.IsDouble()) {
                    valStr = ::ToString(val.AsDouble());
                } else if (val.IsUint64()) {
                    valStr = ::ToString(val.AsUint64());
                }
                AddProperty(doc, cell.first, valStr);
            }
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TGeoYTRowProcessor>;
        static TRegistrator Registrator;

        const TString KeyColumn;
    };

    TGeoYTRowProcessor::TRegistrator TGeoYTRowProcessor::Registrator("geo_yt");

    class TPdsSuggestRowProcessor : public IRowProcessor {
        using IRowProcessor::IRowProcessor;
    public:
        bool NeedConfigFiles() const override { return true; }

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(row.HasKey("Host"));
            Y_ENSURE(row["Host"].IsString());
            Y_ENSURE(row.HasKey("DocUrl"));
            Y_ENSURE(row["DocUrl"].IsString());
            Y_ENSURE(row.HasKey("Body"));
            Y_ENSURE(row.HasKey("Kps"));
            Y_ENSURE(row.HasKey("LastSeenInState"));
            Y_ENSURE(row.HasKey("Language"));
        }

        TAction DoProcessRowSingle(const NYT::TNode& row) override {
            const TString& body = row["Body"].AsString();
            const TString& url = row["Host"].AsString() + row["DocUrl"].AsString();
            const ui64& kps = row["Kps"].AsUint64();
            const ui64& ts = row["LastSeenInState"].AsUint64();
            const ui64& lang = row["Language"].AsUint64();

            NSaas::TAction action;
            action.SetActionType(NSaas::TAction::atModify);
            action.SetPrefix(kps);

            if (!SuggestConfig.Defined()) {
                InitSuggestConfig();
                Y_ENSURE(SuggestConfig.Defined());
            }
            const TString& filteredBody = NSaasHtmlParser::FilterHtmlText(body, SuggestConfig.GetRef());

            auto& doc = action.AddDocument();
            doc.SetUrl(url);
            doc.SetBody(filteredBody);
            doc.SetTimestamp(ts);
            doc.SetMimeType("text/html");
            doc.SetLang(NameByLanguage(ELanguage(lang)));

            return action;
        }

        void InitSuggestConfig() {
            TString configText = Options.OtherOptions;
            auto config = ParseRtyConfig(configText, Options.ExtraOptions);
            TSuggestComponentConfig suggestConfig;

            TAnyYandexConfig yandexConfig;
            if (!yandexConfig.ParseMemory(configText.data())) {
                ythrow yexception() << "cannot parse config";
            }
            TYandexConfig::Section* serverSection = yandexConfig.GetFirstChild("Server");
            TYandexConfig::Section* componentsSection = serverSection->GetAllChildren().find("ComponentsConfig")->second;
            suggestConfig = *config->ComponentsConfig.Get<TSuggestComponentConfig>(SUGGEST_COMPONENT_NAME);
            suggestConfig.Init(*config, componentsSection, SUGGEST_COMPONENT_NAME);
            SuggestConfig = suggestConfig;
        }

    private:
        TMaybe<TSuggestComponentConfig> SuggestConfig;
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TPdsSuggestRowProcessor>;
        static TRegistrator Registrator;
    };

    TPdsSuggestRowProcessor::TRegistrator TPdsSuggestRowProcessor::Registrator("pdssuggest");

    class TMessageRowProcessor : public IRowProcessor {
    public:
        TMessageRowProcessor(const TRowProcessorOptions& options, const TString& dataColumnName = "value")
            : IRowProcessor(options)
            , DataColumnName(dataColumnName)
        {}

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(row.HasKey(DataColumnName));
            Y_ENSURE(row[DataColumnName].IsString());
        }

        TAction DoProcessRowSingle(const NYT::TNode& row) override {
            google::protobuf::LogSilencer logSanitizer;
            const TString& buffer = row[DataColumnName].AsString();
            NRTYServer::TMessage m;
            Y_ENSURE(m.ParseFromArray(buffer.data(), buffer.size()), "Not a valid NRTYServer::TMessage protobuf");

            if (Options.Timestamp != Max<ui64>() && !m.GetDocument().HasModificationTimestamp()) {
                m.MutableDocument()->SetModificationTimestamp(Options.Timestamp);
            }

            return TAction(m);
        }

    private:
        const TString DataColumnName;

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TMessageRowProcessor>;
        static TRegistrator Registrator;
    };

    TMessageRowProcessor::TRegistrator TMessageRowProcessor::Registrator("message");

    class ILogbrokerRowProcessor : public TMessageRowProcessor {
    public:
        using TMessageRowProcessor::TMessageRowProcessor;

    protected:
        TAction DoProcessRowSingle(const NYT::TNode& row) override {
            auto action = TMessageRowProcessor::DoProcessRowSingle(row);
            SetPosition(action, row);
            return action;
        }

    protected:
        virtual void SetPosition(TAction& action, const NYT::TNode& row) = 0;
    };

    class TLogfellerRowProcessor : public ILogbrokerRowProcessor {
    public:
        TLogfellerRowProcessor(const TRowProcessorOptions& options)
            : ILogbrokerRowProcessor(options, "data")
        {}

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            ILogbrokerRowProcessor::DoValidateRow(row);

            Y_ENSURE(row.HasKey("_topic"));
            Y_ENSURE(row.HasKey("_topic_cluster"));
            Y_ENSURE(row.HasKey("_partition"));

            Y_ENSURE(row["_topic"].IsString());
            Y_ENSURE(row["_topic_cluster"].IsString());
            Y_ENSURE(row["_partition"].IsUint64());

            Y_ENSURE(row.HasKey("_offset"));
            Y_ENSURE(row["_offset"].IsUint64());
        }

        void SetPosition(TAction& action, const NYT::TNode& row) override {
            action.SetPosition(GetTopic(row), row["_offset"].AsUint64());
        }

    private:
        TString GetTopic(const NYT::TNode& row) const {
            // legacy topic format: rt3.[cluster]--ident--logtype:partition, cannot contain the character '/' ('/' -> '@')
            TString cluster = row["_topic_cluster"].AsString();
            ui64 partition = row["_partition"].AsUint64();

            TFsPath topicPath = row["_topic"].AsString();
            TString topicDir = topicPath.Parent();
            TString topicName = topicPath.GetName();

            for (auto& c : topicDir) {
                if (c == '/') {
                    c = '@';
                }
            }
            TString topic = Join("--", cluster, topicDir, topicName);
            TString topicWithPartition = Join("", "rt3.", topic, ":", partition);
            return topicWithPartition;
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TLogfellerRowProcessor>;
        static TRegistrator Registrator;
    };

    TLogfellerRowProcessor::TRegistrator TLogfellerRowProcessor::Registrator("logfeller");

    class TLogbrokerRowProcessor : public ILogbrokerRowProcessor {
    public:
        using ILogbrokerRowProcessor::ILogbrokerRowProcessor;

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            ILogbrokerRowProcessor::DoValidateRow(row);

            Y_ENSURE(row.HasKey("key"));
            Y_ENSURE(row.HasKey("subkey"));

            Y_ENSURE(row["key"].IsString());
            Y_ENSURE(row["subkey"].IsString());
        }

        void SetPosition(TAction& action, const NYT::TNode& row) override {
            // Logbroker yt-delivery format: https://st.yandex-team.ru/LOGBROKER-5400#5e5cb454530f3315ce603f47
            auto parts = SplitString(row["subkey"].AsString(), "@@");
            const TString& topic = parts[0];
            ui64 offset = FromString<ui64>(parts[1]);

            action.SetPosition(topic, offset);
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TLogbrokerRowProcessor>;
        static TRegistrator Registrator;
    };

    TLogbrokerRowProcessor::TRegistrator TLogbrokerRowProcessor::Registrator("logbroker");

    class TJsonRowProcessor : public IRowProcessor {
        using IRowProcessor::IRowProcessor;

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(row.HasKey("JsonMessage"));
            Y_ENSURE(row["JsonMessage"].IsString());
        }

        TAction DoProcessRowSingle(const NYT::TNode& row) override {
            const TString& jsonString = row["JsonMessage"].AsString();

            NJson::TJsonValue jsonValue;
            ReadJsonTree(jsonString, &jsonValue, true);
            NSaas::TAction action;
            action.ParseFromJson(jsonValue);

            if (Options.Prefix != 0 && action.HasPrefix()) {
                action.SetPrefix(Options.Prefix);
            }
            auto& doc = action.GetDocument();
            if (Options.Timestamp != Max<ui64>() && !doc.HasTimestamp()) {
                doc.SetTimestamp(Options.Timestamp);
            }
            if (Options.DeadlineMinutesUTC != 0 && !doc.HasDeadlineMinutesUTC()) {
                doc.SetDeadlineMinutesUTC(Options.DeadlineMinutesUTC);
            }

            return action;
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TJsonRowProcessor>;
        static TRegistrator Registrator;
    };

    TJsonRowProcessor::TRegistrator TJsonRowProcessor::Registrator("json");

    class TVitrinaMessageProcessor : public IRowProcessor {
        using IRowProcessor::IRowProcessor;

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            Y_ENSURE(row.HasKey("key"));
            Y_ENSURE(row["key"].IsString());
            Y_ENSURE(row.HasKey("value"));
            Y_ENSURE(row["value"].IsString());
        }

        TAction DoProcessRowSingle(const NYT::TNode& row) override {
            NRTYServer::TMessage msg;

            msg.SetMessageType(enum_cast<NRTYServer::TMessage::TMessageType>(Options.ActionType));

            auto* doc = msg.MutableDocument();
            auto data = row["value"].AsString();

            if (Options.Compression != NMetaProtocol::CM_COMPRESSION_NONE) {
                data = NMetaProtocol::GetDefaultCompressor().Compress(data, Options.Compression);
            }

            doc->SetUrl(row["key"].AsString());
            doc->SetKeyPrefix(Options.Prefix);

            auto* property = doc->AddDocumentProperties();

            property->SetName("data_" + ToString(Options.Compression));
            property->SetValue(data);

            if (row.HasKey("aliases")) {
                Y_VERIFY(row["aliases"].IsList());

                for (const auto& alias : row["aliases"].AsList()) {
                    auto* key = doc->AddAdditionalKeys();

                    key->SetName("uid");
                    key->SetValue(alias.AsString());
                }
            }
            else {
                auto* key = doc->AddAdditionalKeys();

                key->SetName("uid");
                key->SetValue(doc->GetUrl());
            }

            if (Options.Timestamp != Max<ui64>()) {
                doc->SetModificationTimestamp(Options.Timestamp);
            }

            if (Options.DeadlineMinutesUTC != 0) {
                doc->SetDeadlineMinutesUTC(Options.DeadlineMinutesUTC);
            }

            return TAction(msg);
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TVitrinaMessageProcessor>;
        static TRegistrator Registrator;
    };

    TVitrinaMessageProcessor::TRegistrator TVitrinaMessageProcessor::Registrator("vitrina_message");

    class TFaviconDatabaseRowProcessor : public IDataRowProcessor {
        using IDataRowProcessor::IDataRowProcessor;

    private:
        static void ValidateBinaryDataField(const NYT::TNode& value) {
            Y_ENSURE(value.IsString());
            Y_ENSURE(value.AsString().size() > 0);
        }

    protected:
        void DoValidateRow(const NYT::TNode& row) override {
            ValidateBinaryDataField(row["ImageAttrs"]);
            ValidateBinaryDataField(row["ImageDoc"]);
            Y_ENSURE(row["ImageId"].IsUint64());
            Y_ENSURE(row["ImageId"].AsUint64() >= 0);
            Y_ENSURE(row["TargetSize"].IsUint64());
            Y_ENSURE(row["TargetSize"].AsUint64() > 0);
        }

        void DoFillKps(TAction& /*action*/, const NYT::TNode& /*row*/) override {
        }

        void DoFillUrl(TDocument& doc, const NYT::TNode& row) override {
            TStringBuilder url;
            url << row["ImageId"].AsUint64();
            url << '@';
            url << row["TargetSize"].AsUint64();
            doc.SetUrl(url);
        }

        void DoFillBody(TDocument& doc, const NYT::TNode& row) override {
            NFavicon::TImageRecord imageAttrs;
            Y_PROTOBUF_SUPPRESS_NODISCARD imageAttrs.ParseFromString(row["ImageAttrs"].AsString());
            AddProperty(doc, "ImageDoc", row["ImageDoc"].AsString());
            AddProperty(doc, "OriginalHeight", ToString(imageAttrs.GetHeight()));
            AddProperty(doc, "OriginalWidth", ToString(imageAttrs.GetWidth()));
        }

    public:
        bool ShouldSkipRow(const NYT::TNode& row) const override {
            const NYT::TNode& image = row["ImageDoc"];
            return image.IsNull() || !image.IsString() || image.AsString().empty();
        }

    private:
        using TRegistrator = IRowProcessor::TFactory::TRegistrator<TFaviconDatabaseRowProcessor>;
        static TRegistrator Registrator;
    };

    TFaviconDatabaseRowProcessor::TRegistrator TFaviconDatabaseRowProcessor::Registrator("favicon_database");
} // namespace NSaas
