#pragma once

#include <saas/deploy_manager/meta/cluster.h>
#include <saas/deploy_manager/modules/service_discovery/sd_module.h>
#include <saas/deploy_manager/scripts/common/deploy/abstract/deploy_factory.h>
#include <saas/deploy_manager/scripts/common/global_names/global_names.h>
#include <saas/deploy_manager/storage/abstract.h>
#include <saas/library/searchmap/host.h>
#include <saas/library/sharding/sharding.h>
#include <library/cpp/json/json_value.h>


namespace NRTYDeploy {
    const bool USE_CONTAINERS_DEFAULT_VALUE = true;

    class TTagsInfo {
    public:
        struct TTag {
        private:
            void BuildAliasByTag();
        public:
            enum TType {
                NANNY_SERVICE = 1 << 0
            };

            TType Type;
            TString Name;
            TString Configuration;
            TString Alias;

            explicit TTag(TStringBuf info, TType type = NANNY_SERVICE);
            explicit TTag(const NJson::TJsonValue::TMapType& mapInfo, TType type = NANNY_SERVICE);
            bool operator<(const TTag& other) const;
            bool operator==(const TTag& other) const;
            TString ToString() const {
                return TString::Join(Name, "@", Configuration , "@", Alias);
            }
        };

        class TServiceResourcesInfo {
        private:
            TVector<TTag> Tags;
            bool UseContainers;
            TVector<NRTYCluster::TSlotData> SlotList;
            TVector<NRTYCluster::TSlotData> ExcludeSlots;
            TVector<NRTYCluster::TSlotData> EndpointSets;

        private:
            static bool ParseList(const NJson::TJsonValue& value, TVector<NRTYCluster::TSlotData>& result, bool isEndpointSet=false);
            static bool ParseBool(const NJson::TJsonValue& value, bool& result);
            static bool ParseTags(const NJson::TJsonValue& value, TTag::TType type, TVector<TTag>& tags);

        public:

            using TPtr = TAtomicSharedPtr<TServiceResourcesInfo>;

            TServiceResourcesInfo(const TVector<TTag>& tags, bool useContainers,
                    const TVector<NRTYCluster::TSlotData>& slotList, const TVector<NRTYCluster::TSlotData>& excludeSlots,
                    const TVector<NRTYCluster::TSlotData>& endpointSets)
                : Tags(tags)
                , UseContainers(useContainers)
                , SlotList(slotList)
                , ExcludeSlots(excludeSlots)
                , EndpointSets(endpointSets)
            {
                SortUnique(Tags);
                SortUnique(SlotList);
                SortUnique(ExcludeSlots);
                SortUnique(EndpointSets);
            }

            TServiceResourcesInfo(TVector<TTag>&& tags, bool useContainers,
                    TVector<NRTYCluster::TSlotData>&& slotList, TVector<NRTYCluster::TSlotData>&& excludeSlots,
                    TVector<NRTYCluster::TSlotData>&& endpointSets)
                : Tags(std::move(tags))
                , UseContainers(useContainers)
                , SlotList(std::move(slotList))
                , ExcludeSlots(std::move(excludeSlots))
                , EndpointSets(std::move(endpointSets))
            {
                SortUnique(Tags);
                SortUnique(SlotList);
                SortUnique(ExcludeSlots);
                SortUnique(EndpointSets);
            }

            TServiceResourcesInfo operator =(TServiceResourcesInfo& other) = delete;

            const TVector<TTag>& GetTags() const {
                return Tags;
            }

            bool GetUseContainers() const {
                return UseContainers;
            }

            const TVector<NRTYCluster::TSlotData>& GetSlotList() const {
                return SlotList;
            }

            const TVector<NRTYCluster::TSlotData>& GetExcludeSlots() const {
                return ExcludeSlots;
            }

            const TVector<NRTYCluster::TSlotData>& GetEndpointSets() const {
                return EndpointSets;
            }

            static TPtr Deserialize(const NJson::TJsonValue& json);
            void Serialize(NJson::TJsonValue& json) const;
        };

    private:
        const IVersionedStorage& Storage;
        TVector<TString> CTypes;
        mutable TRWMutex MutexCacheServiceTypeInfo;
        mutable TMap<TString, TServiceResourcesInfo::TPtr> CacheServiceTypeInfo;
        mutable TRWMutex MutexCacheServiceInfo;
        mutable TMap<TString, TServiceResourcesInfo::TPtr> CacheServiceInfo;
    public:
        TTagsInfo(const IVersionedStorage& storage);
        TServiceResourcesInfo::TPtr GetCTypeTagsInfo(const TString& ctype, const TString& serviceClass) const;
        TServiceResourcesInfo::TPtr GetServiceTagsInfo(const TString& service, const TString& ctype, bool logError) const;
        void SaveCTypeTagsInfo(const TString& ctype, const TString& serviceClass, const TServiceResourcesInfo& info) const;
        void SaveServiceTagsInfo(const TString& service, const TString& ctype, const TServiceResourcesInfo& info) const;
        const TVector<TString>& GetCTypes() const {
            return CTypes;
        }
        void InvalidateCTypeTagsInfo(const TString& ctype, const TString& serviceClass);
        void InvalidateServiceTagsInfo(const TString& service, const TString& ctype);
    };

    class TResourcesManager {
    private:
        const NRTYDeploy::ICommonData& EnvironmentInformation;

        TRWMutex TagsCacheMutex;
        mutable TMap<TString, TVector<NRTYCluster::TSlotData>> Cache;

        TTagsInfo TagsInfo;

        struct TSTSInfo {
        private:
            const TString ServiceType;
            const TString ServiceName;
            mutable THolder<NRTYCluster::TCluster> Cluster;
            mutable TVector<TString> CTypes;
            const TResourcesManager& ResourcesManager;
            TRWMutex Mutex;
        public:

            using TPtr = TAtomicSharedPtr<TSTSInfo>;

            TSTSInfo(const TString& serviceType, const TString& serviceName, const TResourcesManager& resourcesManager)
                : ServiceType(serviceType)
                , ServiceName(serviceName)
                , ResourcesManager(resourcesManager)
            {

            }

            NRTYCluster::TCluster GetCluster() const;
        };

        TRWMutex ClustersCacheMutex;
        mutable TMap<TString, TSTSInfo::TPtr> Clusters;
        TRWMutex AntiDDosMutex;
        mutable TMap<TString, ui32> UpdateTimes;
    private:
        bool GetSlotList(const TString& ctype, const TString& serviceType, const TString& serviceName, NRTYCluster::TCluster& result) const;
        bool GetSlotListFromCacheUnsafe(const TTagsInfo::TTag& tag, TVector<NRTYCluster::TSlotData>& slotsResult) const;
    public:
        using TSlotsInfo = TVector<NSaas::TSlotInfo>;

        TResourcesManager(const NRTYDeploy::ICommonData& environmentInformation)
            : EnvironmentInformation(environmentInformation)
            , TagsInfo(EnvironmentInformation.GetStorage())
        {
            INFO_LOG << "Caches initialization start..." << Endl;
            TVector<TString> services;
            TResourcesManager::GetServices(EnvironmentInformation.GetStorage(), services);
            TSet<TString> servicesTypes;
            Singleton<NRTYDeployInfo::IDeployComponentInfoAbstract::TFactory>()->GetKeys(servicesTypes);

            for (auto&& ctype : TagsInfo.GetCTypes()) {
                TVector<TTagsInfo::TServiceResourcesInfo::TPtr> infos;
                for (auto&& service : services) {
                    TTagsInfo::TServiceResourcesInfo::TPtr infoPtr = TagsInfo.GetServiceTagsInfo(service, ctype, false);
                    if (infoPtr) {
                        infos.push_back(std::move(infoPtr));
                    }
                }

                for (auto&& st : servicesTypes) {
                    TTagsInfo::TServiceResourcesInfo::TPtr infoPtr = TagsInfo.GetCTypeTagsInfo(ctype, st);
                    if (infoPtr) {
                        infos.push_back(std::move(infoPtr));
                    }
                }
                for (auto&& resInfoPtr : infos) {
                    NRTYCluster::TCluster cluster;
                    for (auto&& tag : resInfoPtr->GetTags()) {
                        GetSlotList(tag, ctype, resInfoPtr->GetUseContainers(), resInfoPtr->GetExcludeSlots(), cluster);
                    }
                    for (auto&& eps : resInfoPtr->GetEndpointSets()) {
                        EnvironmentInformation.GetSDModule().AddEndpointSet(eps.FullHost());
                    }
                }
            }

            NRTYCluster::TCluster cluster;
            INFO_LOG << "Caches initialization finished" << Endl;

        }

        NRTYCluster::TCluster GetCluster(const TString& serviceType, const TString& serviceName) const;
        TSTSInfo::TPtr NewClusterInfo(const TString& serviceType, const TString& serviceName) const;
        void InvalidateCaches(const TString& ctype, const TString& serviceType, const TString& serviceName, bool reloadAfter=false);

        bool GetAvailableSlotsForService(const TString& ctype, const TString& serviceType, const TString& serviceName, NRTYCluster::TCluster& result) const {
            NRTYCluster::TCluster cluster = GetCluster(serviceType, serviceName);
            NRTYCluster::TCTypeCluster cTypeCluster;
            if (!cluster.GetCTypeCluster(ctype, cTypeCluster))
                return false;
            for (auto&& i : cTypeCluster.GetSlots()) {
                result.RegisterSlot(ctype, i.second);
            }
            return true;
        }

        bool GetFreeSlotsForService(const TString& ctype, const TString& serviceType, const TString& serviceName, const NSearchMapParser::TSearchMap& usedSm, NRTYCluster::TCluster& result) const {
            NRTYCluster::TCluster cluster = GetCluster(serviceType, serviceName);
            NRTYCluster::TCTypeCluster cTypeCluster;
            if (!cluster.GetCTypeCluster(ctype, cTypeCluster))
                return false;

            auto freeSlots = cTypeCluster.GetSlots();
            TMap<TString, NRTYCluster::TSlotData> unusedSlots;

            auto&& slotsSM = usedSm.GetSlots();
            bool isUnused = (serviceName == UNUSED_SERVICE);

            for (auto&& i : slotsSM) {
                auto it = freeSlots.find(i.GetShortSlotName());
                if (it != freeSlots.end()) {
                    if (isUnused) {
                        unusedSlots.insert(*it);
                    } else {
                        freeSlots.erase(it);
                    }
                }
            }
            if (isUnused)
                freeSlots = unusedSlots;

            result.RegisterCType(ctype);

            for (auto&& i : freeSlots) {
                result.RegisterSlot(ctype, i.second);
            }

            return true;
        }

        bool GetSlotList(const TTagsInfo::TTag& tag, const TString& ctype, const bool useContainerNames, const TVector<NRTYCluster::TSlotData>& excludes, NRTYCluster::TCluster& result) const;
        TTagsInfo::TServiceResourcesInfo::TPtr GetServiceResourcesInfo(const TString& ctype, const TString& serviceType, const TString& serviceName) const;
        void SaveServiceResourcesInfo(const TString& ctype, const TString& serviceType, const TString& serviceName, const TTagsInfo::TServiceResourcesInfo& info) const;
        const TVector<TString>& GetCTypes() const {
            return TagsInfo.GetCTypes();
        }

        static bool GetServices(const IVersionedStorage& storage, TVector<TString>& services);
    };
}

