#pragma once

#include <saas/protos/cluster.pb.h>
#include <saas/library/sharding/sharding.h>
#include <saas/library/searchmap/searchmap.h>
#include <saas/library/searchmap/parsers/parser.h>
#include <saas/util/types/parameter.h>

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

#include <util/generic/vector.h>

namespace NSearchMapParser {
    using NUtil::IParameter;
    using NUtil::TParameter;
    using NUtil::TSynonymParameter;
    using NUtil::TClampedParameter;
    using NUtil::TLazyDefaultParameter;

    const ui64 CurrentJsonFormatVersion = 2;

    struct TSearchMapProperties {
        ui64 Version;

        TSearchMapProperties()
            : Version(CurrentJsonFormatVersion)
        {}
    };

    const TParsingSettings DefaultParsingSettings = TParsingSettings();

    template <class T>
    class TSubParsers: public TVector<TSimpleSharedPtr<T> > {};

    template <class TSuccessor>
    class TSearchMapCommonParser: public NUtil::TParser<TSuccessor> {
    public:
        TSearchMapCommonParser(const TParsingSettings& settings)
            : ParsingSettings(settings)
        {}
        void ParseParameters(const NJson::TJsonValue& json) {
            for (auto&& p : NUtil::TParser<TSuccessor>::Parameters) {
                NJson::TJsonValue parameterJson;
                if (!json.GetValue(p.first, &parameterJson))
                    continue;
                TString parsedValue;
                switch (parameterJson.GetType())
                {
                case NJson::JSON_BOOLEAN:
                    parsedValue = ToString(parameterJson.GetBoolean());
                    break;
                case NJson::JSON_INTEGER:
                    parsedValue = ToString(parameterJson.GetInteger());
                    break;
                case NJson::JSON_UINTEGER:
                    parsedValue = ToString(parameterJson.GetUInteger());
                    break;
                case NJson::JSON_STRING:
                    parsedValue = parameterJson.GetString();
                    break;
                default:
                    throw yexception() << "unsupported json field type " << (int)parameterJson.GetType() << " for parameter " << p.first;
                    break;
                }
                p.second->Parse(parsedValue);
            }
        }
        template <class TArrayItem>
        inline void ParseJsonArray(const NJson::TJsonValue& json, TSubParsers<TArrayItem>& container) {
            if (!json.IsArray() && !ParsingSettings.IgnoreParsingErrors)
                throw yexception() << "target section must be an array";
            const NJson::TJsonValue::TArray& jsonArray = json.GetArray();
            for (NJson::TJsonValue::TArray::const_iterator i = jsonArray.begin(); i != jsonArray.end(); ++i)
                container.push_back(new TArrayItem(*i, ParsingSettings));
        }
        template <class TArrayItem, typename TPred>
        inline void ParseJsonMapIf(const NJson::TJsonValue& json, TSubParsers<TArrayItem>& container, TPred condition) {
            Y_ENSURE(json.IsMap() || ParsingSettings.IgnoreParsingErrors, "target section must be a map");
            const NJson::TJsonValue::TMapType& jsonMap = json.GetMap();
            for (auto&& [sectionName, content]: jsonMap) {
                if (!condition(sectionName))
                    continue;
                container.push_back(new TArrayItem(sectionName, content, ParsingSettings));
            }
        }
        template <class TArrayItem>
        inline void ParseJsonMap(const NJson::TJsonValue& json, TSubParsers<TArrayItem>& container) {
            return ParseJsonMapIf(json, container, [](const TString&) { return true; });
        }

    protected:
        TParsingSettings ParsingSettings;
    };

    class TSearchMapHostParser: public TSearchMapCommonParser<TSearchMapHostParser> {
    public:
        TSearchMapHostParser(const NJson::TJsonValue& host, const TParsingSettings& settings);

        TSearchMapHost Get() const;
    private:
        TParameter<TThis, TString> Host;
        TClampedParameter<TThis, TShardIndex> ShardMin;
        TClampedParameter<TThis, TShardIndex> ShardMax;
        TParameter<TThis, RankType> Rank;
        TParameter<TThis, ui16> SearchPort;
        TLazyDefaultParameter<TThis, ui16> IndexerPort;
        TParameter<TThis, TString> Group;
        TParameter<TThis, bool> DisableSearch;
        TParameter<TThis, bool> DisableIndexing;
        TParameter<TThis, bool> DisableSearchFiltration;
        TParameter<TThis, bool> DisableFetch;
        TParameter<TThis, bool> IsSd;
    };

    class TSearchMapReplicaParser: public TSearchMapCommonParser<TSearchMapReplicaParser> {
    public:
        TSearchMapReplicaParser(const TString& alias, const NJson::TJsonValue& replica, const TParsingSettings& settings);

        TSearchMapReplica Get() const;
    private:
        TString Alias;

        TParameter<TThis, TString> CustomConfig;
        TParameter<TThis, TString> Features;
        TParameter<TThis, ui32> UpdateFrequency;
        TParameter<TThis, bool> DisableSearch;
        TParameter<TThis, bool> DisableIndexing;

        TSubParsers<TSearchMapHostParser> Hosts;
    };

    class TSearchMapMetaComponentParser: public TSearchMapCommonParser<TSearchMapMetaComponentParser> {
    public:
        TSearchMapMetaComponentParser(const NJson::TJsonValue& component, const TParsingSettings& settings);

        TMetaComponent Get() const;
    private:
        TParameter<TThis, TString> ServiceName;
        TParameter<TThis, ui32> SearchPriority;
        TParameter<TThis, bool> DisableSearch;
        TParameter<TThis, bool> DisableIndexing;
        TParameter<TThis, bool> IndexReplyIgnoring;
    };

    class TSearchMapCommonServiceParser: public TSearchMapCommonParser<TSearchMapCommonServiceParser> {
    public:
        TSearchMapCommonServiceParser(const TString& name, const NJson::TJsonValue& service, const TParsingSettings& settings);
        void FillCommonService(TServiceSpecificOptions& service) const;

    protected:
        TString Name;

        TParameter<TThis, ui16> SearchPort;
        TLazyDefaultParameter<TThis, ui16> IndexerPort;
        TParameter<TThis, bool> IndexAttaches;
        TParameter<TThis, bool> RequireAuth;
        TParameter<TThis, TString> SearchProtocol;
        TParameter<TThis, TString> ShardBy;
        TParameter<TThis, DispatchTarget> IndexingTarget;
        TSynonymParameter<TThis, TString> Servers;
        TSynonymParameter<TThis, TString> Stream;
        TParameter<TThis, bool> PerDcSearch;
        TParameter<TThis, NSaas::ShardsCount> Shards;
        TParameter<TThis, ui32> NumTopics;
        TParameter<TThis, bool> SkipPing;
        TServiceSpecificOptions::TWeightsByAliasFrom WeightsByAlias;

    };

    class TSearchMapServiceParser: public TSearchMapCommonServiceParser {
    public:
        TSearchMapServiceParser(const TString& name, const NJson::TJsonValue& service, const TParsingSettings& settings);

        TSearchMapService Get() const;
    private:
        TSubParsers<TSearchMapReplicaParser> Replicas;
    };

    class TSearchMapMetaServiceParser: public TSearchMapCommonServiceParser {
    public:
        TSearchMapMetaServiceParser(const TString& name, const NJson::TJsonValue& service, const TParsingSettings& settings);

        TMetaService Get() const;
    private:
        TSubParsers<TSearchMapMetaComponentParser> Components;
    };

    class TSearchMapPropertiesParser: public TSearchMapCommonParser<TSearchMapPropertiesParser> {
    public:
        TSearchMapPropertiesParser(const NJson::TJsonValue& properties, const TParsingSettings& parsingSettings);

        TSearchMapProperties Get() const;
    private:
        TParameter<TThis, ui64> Version;
    };

    class TJsonSearchMapParser: public ISearchMapParser,
                                public TSearchMapCommonParser<TJsonSearchMapParser>
    {
    public:
        // ISearchMapParser
        virtual void OpenSearchMap(IInputStream& stream) override;
        virtual TSearchMap GetSearchMap(bool checkIndex = true, bool checkSearch = true) const override;

    public:
        explicit TJsonSearchMapParser(const TParsingSettings& settings = DefaultParsingSettings);
        explicit TJsonSearchMapParser(const TSearchMap& searchMap);
        explicit TJsonSearchMapParser(const TString& fileName);
        explicit TJsonSearchMapParser(IInputStream& stream);
        explicit TJsonSearchMapParser(const NJson::TJsonValue& jsonValue);
        NJson::TJsonValue SerializeToJson() const;
        TString SerializeToString() const;

        void ExpandSearchMap(TShardIndex shardsNumber);
        void RescaleSearchMap(TShardIndex shardsNumber);
        bool VerifySearchMap(TString& error, bool checkIndex = true, bool checkSearch = true) const;
        void ParseSearchMap(IInputStream& stream);
        void ParseSearchMap(const TString& searchMap);
        void ParseSearchMap(const NJson::TJsonValue& searchMap);

        static NJson::TJsonValue SerializeService(const TSearchMapService& service, bool VerboseSerialization = false);
        static NJson::TJsonValue SerializeMetaService(const TMetaService& service, bool VerboseSerialization = false);
        static NJson::TJsonValue SerializeReplica(const TSearchMapReplica& replica, bool VerboseSerialization = false);
        static NJson::TJsonValue SerializeMetaComponent(const TMetaComponent& component, bool VerboseSerialization = false);
        static NJson::TJsonValue SerializeHost(const TSearchMapHost& host, bool VerboseSerialization = false);
        static NJson::TJsonValue SerializeProperties(const TSearchMapProperties& properties, bool VerboseSerialization = false);
        static void SerializeCommonService(NJson::TJsonValue& result, const TServiceSpecificOptions& options, bool VerboseSerialization = false);

        // version support
        void UpdateVersion();
        void MergeReplicas();
        void MergePorts();
    public:
        bool VerboseSerialization;
    protected:
        TSubParsers<TSearchMapServiceParser> Services;
        TSubParsers<TSearchMapMetaServiceParser> MetaServices;
        TSearchMapProperties Properties;
        TVector<TSearchMapService> InternalSearchMap;
        TVector<TMetaService> InternalMetaSearchMap;
    };
}
