#pragma once

#include <saas/deploy_manager/storage/abstract.h>
#include <saas/library/searchmap/searchmap.h>
#include <saas/library/sharding/sharding.h>
#include <saas/util/cluster/cluster.h>
#include <saas/util/types/interval.h>
#include <library/cpp/json/json_writer.h>
#include <library/cpp/logger/global/global.h>

#include <util/generic/set.h>
#include <util/generic/vector.h>

namespace NSaas {

    class TMetaLink {
    private:
        NRTYCluster::TSlotData Slot;
        bool Enabled;
    public:
        TMetaLink() {
            Enabled = true;
        }

        const NRTYCluster::TSlotData& GetSlot() const {
            return Slot;
        }

        bool GetEnabled() const {
            return Enabled;
        }

        bool Deserialize(const NSaasProto::TMetaLink& link) {
            Slot = NRTYCluster::TSlotData(link.GetSlot().GetHost(), link.GetSlot().GetPort());
            Enabled = link.GetEnabled();
            return true;
        }

        NSaasProto::TMetaLink SerializeToProto() const {
            NSaasProto::TMetaLink result;
            result.SetEnabled(Enabled);
            result.MutableSlot()->SetHost(Slot.FullSlotName());
            result.MutableSlot()->SetPort(Slot.Port);
            return result;
        }
    };

    class TMetaSlot {
    private:
        NRTYCluster::TSlotData Slot;
        bool Enabled;
        TMap<TString, TMetaLink> Links;
    public:

        const NRTYCluster::TSlotData& GetSlot() const {
            return Slot;
        }

        bool GetEnabled() const {
            return Enabled;
        }

        bool Deserialize(const NSaasProto::TMetaSlot& metaSlot) {
            Slot = NRTYCluster::TSlotData(metaSlot.GetSlot().GetHost(), metaSlot.GetSlot().GetPort());
            Enabled = metaSlot.GetEnabled();
            for (ui32 link = 0; metaSlot.SourcesSize(); ++link) {
                TMetaLink metaLink;
                if (!metaLink.Deserialize(metaSlot.GetSources(link))) {
                    return false;
                }
                Links[metaLink.GetSlot().ShortSlotName()] = metaLink;
            }
            return true;
        }

        NSaasProto::TMetaSlot SerializeToProto() const {
            NSaasProto::TMetaSlot result;
            result.MutableSlot()->SetHost(Slot.FullSlotName());
            result.MutableSlot()->SetPort(Slot.Port);
            result.SetEnabled(Enabled);

            for (auto& i : Links) {
                *result.AddSources() = i.second.SerializeToProto();
            }
            return result;
        }
    };

    class TCluster {
    public:
        typedef TMap<TString, NSearchMapParser::TSearchMapService> TServices;
        typedef TMap<TString, NSearchMapParser::TMetaService> TMetaServices;
        struct TSearchMaps {
            NSearchMapParser::TSearchMap RTY;
            NSearchMapParser::TSearchMap SP;
            NSearchMapParser::TSearchMap IP;
            NSearchMapParser::TSearchMap Meta;
            NSearchMapParser::TSearchMap IntMeta;
        };

    private:
        TServices Services;
        TServices MetaInt;
        TMetaServices MetaServices;

    public:
        typedef TAtomicSharedPtr<TCluster> TPtr;

        TCluster() {}

        const NSearchMapParser::TMetaService& AddMetaService(const NSearchMapParser::TMetaService& ms) {
            MetaServices[ms.Name] = ms;
            return MetaServices[ms.Name];
        }

        bool IsMetaServiceExist(const TString& service) const {
            return MetaServices.contains(service);
        }

        NSearchMapParser::TMetaService* MutableMetaService(const TString& name) {
            auto i = MetaServices.find(name);
            return i == MetaServices.end() ? nullptr : &i->second;
        }

        bool RemoveMetaservice(const TString& name) {
            auto i = MetaServices.find(name);
            if (i == MetaServices.end())
                return false;
            MetaServices.erase(i);
            return true;
        }

        bool Deserialize(const NSaasProto::TCluster& cluster);

        NSaasProto::TCluster SerializeToProto() const;

        TCluster(const TString& serialized) {
            Deserialize(serialized);
        }

        TCluster(const NSearchMapParser::TSearchMap& sm) {
            for (auto& i : sm.GetInternalSearchMap()) {
                Services[i.Name] = i;
            }
        }

        TString Serialize() const;
        bool Deserialize(const TString& string);

        bool Validate(TString& error) const;

        void BuildSearchMaps(TSearchMaps& searchMaps, bool compile) const;

        NSearchMapParser::TSearchMapService* IntMetaServiceMutable(const TString& service) {
            if (!service)
                return nullptr;
            auto i = MetaInt.find(service);
            if (i != MetaInt.end())
                return &MetaInt[service];
            NSearchMapParser::TSearchMapService serviceNew;
            serviceNew.Name = service;
            MetaInt[service] = serviceNew;
            return &MetaInt[service];
        }

        NSearchMapParser::TSearchMapService* ServiceSearchMap(const TString& service) {
            auto i = Services.find(service);
            if (i != Services.end())
                return &i->second;
            return nullptr;
        }

        const NSearchMapParser::TSearchMapService* ServiceSearchMap(const TString& service) const {
            auto i = Services.find(service);
            if (i != Services.end())
                return &i->second;
            return nullptr;
        }

        bool RemoveRtyService(const TString& service) {
            auto i = Services.find(service);
            if (i != Services.end()) {
                Services.erase(i);
                return true;
            }
            else {
                return false;
            }
        }

        bool RemoveIntService(const TString& service) {
            auto i = MetaInt.find(service);
            if (i != MetaInt.end()) {
                MetaInt.erase(i);
                return true;
            }
            else {
                return false;
            }
        }

        NSearchMapParser::TSearchMapService* AddRTYService(const TString& service) {
            if (!service)
                return nullptr;
            auto i = Services.find(service);
            if (i != Services.end())
                return &(i->second);
            NSearchMapParser::TSearchMapService serviceNew;
            serviceNew.Name = service;
            Services[service] = serviceNew;
            return &Services[service];
        }

        const NSearchMapParser::TSearchMapService& AddRTYService(const NSearchMapParser::TSearchMapService& rtys) {
            return Services[rtys.Name] = rtys;
        }

        bool IsRTYServiceExist(const TString& service) const {
            return Services.contains(service);
        }

        NSearchMapParser::TSearchMapService* AddIntService(const TString& service) {
            if (!service)
                return nullptr;
            auto i = MetaInt.find(service);
            if (i != MetaInt.end())
                return &(i->second);
            NSearchMapParser::TSearchMapService serviceNew;
            serviceNew.Name = service;
            MetaInt[service] = serviceNew;
            return &MetaInt[service];
        }

        const TServices& GetServices() const {
            return Services;
        }

        const TServices& GetMetaInt() const {
            return MetaInt;
        }
    };

    class TClusterConst {
    public:

        typedef TAtomicSharedPtr<TClusterConst> TPtr;

        TClusterConst() {}

        bool Deserialize(const NSaasProto::TCluster& cluster);

        TClusterConst(const TString& serialized) {
            Deserialize(serialized);
        }

        bool Deserialize(const TString& string);

        const NSearchMapParser::TSearchMap& SPSearchMap() const {
            return SearchMapsCache.SP;
        }

        const NSearchMapParser::TSearchMap& IPSearchMap() const {
            return SearchMapsCache.IP;
        }

        const NSearchMapParser::TSearchMap& IntMetaSearchMap() const {
            return SearchMapsCache.IntMeta;
        }

        const NSearchMapParser::TSearchMap& MetaServiceSearchMap() const {
            return SearchMapsCache.Meta;
        }

        NSearchMapParser::TSearchMap IntMetaSearchMap(const TString& slotName) const {
            NSearchMapParser::TSearchMap result;
            for (auto& service : Cluster.GetMetaInt()) {
                NSearchMapParser::TSearchMapHost host;
                if (service.second.GetHostBySlot(slotName, host)) {
                    const NSearchMapParser::TSearchMapService* serviceInfo = ServiceSearchMap(service.first);
                    if (serviceInfo) {
                        auto& serviceInfoMutable = result.AddService(service.first);
                        serviceInfoMutable = *serviceInfo;
                        for (ui32 i = 0; i < serviceInfoMutable.Replicas.size(); ++i) {
                            auto& replica = serviceInfoMutable.Replicas[i];
                            for (ui32 j = 0; j < replica.Hosts.size();) {
                                if (!replica.Hosts[j].Shards.Intersection(host.Shards)) {
                                    replica.Hosts.erase(replica.Hosts.begin() + j);
                                } else {
                                    ++j;
                                }
                            }
                        }
                    }
                }
            }
            return result;
        }

        const NSearchMapParser::TSearchMap& RTYSearchMap() const {
            return SearchMapsCache.RTY;
        }

        const NSearchMapParser::TSearchMapService* ServiceSearchMap(const TString& service) const {
            auto i = Cluster.GetServices().find(service);
            if (i != Cluster.GetServices().end())
                return &i->second;
            return nullptr;
        }

        const TCluster& GetCluster() const {
            return Cluster;
        }

    private:
        TCluster Cluster;
        TCluster::TSearchMaps SearchMapsCache;
    };

    class TClusterLocker {
    private:
        NRTYDeploy::TAbstractLock::TPtr Lock;
        TCluster::TPtr Cluster;
        TString CType;
        mutable TString InitialContent;
        const NRTYDeploy::ICommonData* CommonData = nullptr;
    public:
        typedef TAtomicSharedPtr<TClusterLocker> TPtr;

        bool IsCorrect() const {
            return !!Cluster;
        }

        bool Save() const {
            VERIFY_WITH_LOG(!!CommonData, "Incorrect usage cluster locker");
            VERIFY_WITH_LOG(!!Cluster, "Incorrect usage cluster locker");
            TString saveData = Cluster->Serialize();
            DEBUG_LOG << "Save " << GetFilePath(CType) << ", inital:\n" << InitialContent << "\nnew:\n" << saveData << Endl;
            if (saveData != InitialContent) {
                if(!CommonData->GetStorage().SetValue(GetFilePath(CType), saveData, true, false)) {
                    DEBUG_LOG << "Can't store cluster_meta" << Endl;
                    return false;
                }
                InitialContent = saveData;
            }
            return true;
        }

        TString GetContent() const {
            return InitialContent;
        }

        static const TClusterConst* DefineConst(const NRTYDeploy::ICommonData& common, const TString& ctype, i64 version = -1);

        static TClusterLocker DefineMutable(const NRTYDeploy::ICommonData& common, const TString& ctype);

        static void ClearCaches();

        static bool GetVersion(const NRTYDeploy::ICommonData& common, const TString& ctype, i64& version) {
            bool result = common.GetStorage().GetVersion(GetFilePath(ctype), version);
            DEBUG_LOG << GetFilePath(ctype) << " version " << version << Endl;
            return result;

        }

        TCluster::TPtr& operator->() {
            VERIFY_WITH_LOG(!!Cluster, "Incorrect cluster lock usage");
            return Cluster;
        }

        const TCluster& operator*() const {
            VERIFY_WITH_LOG(!!Cluster, "Incorrect cluster lock usage");
            return *Cluster;
        }

        TCluster& operator*() {
            VERIFY_WITH_LOG(!!Cluster, "Incorrect cluster lock usage");
            return *Cluster;
        }

        bool operator!() const {
            return !IsCorrect();
        }

        const TCluster::TPtr& operator->() const {
            VERIFY_WITH_LOG(!!Cluster, "Incorrect cluster lock usage");
            return Cluster;
        }
    private:
        class TCacheCallbacks;
        static TString GetFilePath(const TString& ctype) {
            return "/common/" + ctype + "/cluster.meta";
        }

    };

}
