#include "service.h"

#include <saas/library/persqueue/common/common.h>
#include <saas/library/persqueue/common/installations.h>
#include <saas/library/persqueue/configuration/service/modify_manager/logbroker/entity.h>
#include <saas/library/persqueue/configuration/service/modify_manager/logbroker/read_rules.h>
#include <saas/library/persqueue/configuration/service/modify_manager/logbroker/remote_mirror_rules.h>
#include <saas/library/persqueue/configuration/service/modify_manager/logbroker/permissions.h>
#include <saas/library/persqueue/configuration/namespace/namespace.h>
#include <saas/library/persqueue/writer/shards_info.h>
#include <saas/util/json/json.h>
#include <saas/util/network/http_request.h>

#include <library/cpp/logger/global/global.h>
#include <library/cpp/zookeeper/zookeeper.h>

#include <util/string/join.h>

namespace {
    template <class T>
    TVector<T> GetEntitiesDifference(const TVector<T>& a, const TVector<T>& b) {
        TSet<T> setA(a.begin(), a.end());
        TSet<T> setB(b.begin(), b.end());
        TVector<T> result;
        std::set_difference(setA.begin(), setA.end(), setB.begin(), setB.end(),  std::back_inserter(result));
        return result;
    }

    TVector<TString> TvmToSubjects(TVector<ui32> ids) {
        TVector<TString> subjects;
        for (auto id : ids) {
            subjects.push_back(Join("@", id, "tvm"));
        }
        return subjects;
    }

    NJson::TJsonValue MakeRequest(const TString& host, const TString& query) {
        NUtil::THttpRequest request(query);
        request.SetTimeout(TDuration::Minutes(1).MilliSeconds());
        NUtil::THttpReply reply = request.Execute(host, 80);
        if (!reply.IsSuccessReply()) {
            ythrow yexception() << "Cannot process request " << host << "/" << query << " : " << reply.ErrorMessage();
        }
        return NUtil::JsonFromString(reply.Content());
    }

    NJson::TJsonValue GetInstancesFromDM(const TString& host, const TString& ctype, const TString& service) {
        TString query = Join("", "api/slots_by_interval/?service_trype=rtyserver&ctype=", ctype, "&service=", service);
        return MakeRequest(host, query);
    }

    NJson::TJsonValue GetInstancesFromGencfg(const TString& group, const TString& tag) {
        const TString host = "api.gencfg.yandex-team.ru";
        TString query = Join("/", tag, "searcherlookup/groups", group, "instances");
        return MakeRequest(host, query);
    }

    void FillInstancesInfoFromDM(const TString& host, const TString& ctype, const TString& service, ui32& replicasResult, TVector<TString>& shardsResult) {
        auto instantes = GetInstancesFromDM(
            host,
            ctype,
            service
        );
        TVector<TString> shards;
        ui32 maxReplicas = 0;
        for (auto& shard : instantes.GetArray()) {
            shards.push_back(shard["id"].GetString());
            ui32 replicas = 0;
            for (auto& replica : shard["slots"].GetArray()) {
                if (replica.Has("is_sd") && replica["is_sd"].GetBoolean()) {
                    continue;
                }
                replicas++;
            }
            maxReplicas = Max<ui32>(maxReplicas, replicas);
        }
        INFO_LOG << "Received from deploymanager service info: replicas=" << maxReplicas
            << " shards=" << JoinRange(",", shards.begin(), shards.end()) << Endl;
        if (shardsResult.empty()) {
            shardsResult = shards;
        }
        if (!replicasResult) {
            replicasResult = maxReplicas;
        }
    }

    void FillInstancesInfoFromGencfg(const TVector<TString>& groups, std::optional<TString> gencfgTag, ui32& replicasResult, TVector<TString>& shardsResult) {
        const TString unknownLabel = "unknown";

        TString gencfgTagStr = "trunk";
        if (gencfgTag) {
            gencfgTagStr = gencfgTag.value();
            if (gencfgTagStr != "trunk" && gencfgTagStr.StartsWith("stable-")) {
                gencfgTagStr = "tags/" + gencfgTagStr;
            }
        }

        THashMap<TString, ui32> shards;
        ui32 replicas = 0;
        for (auto group : groups) {
            auto instances = GetInstancesFromGencfg(group, gencfgTagStr);
            for (auto& instance : instances["instances"].GetArraySafe()) {
                TString shard = unknownLabel;
                for (auto& tag : instance["tags"].GetArraySafe()) {
                    auto tagStr = tag.GetString();
                    if (tagStr.StartsWith("OPT_shardid")) {
                        shard = tagStr;
                        break;
                    }
                }
                shards[shard]++;
                replicas = Max<ui32>(replicas, shards[shard]);
            }
        }
        Y_VERIFY(!shards.contains(unknownLabel) || shards.size() == 1, "Has some instances without tag: OPT_shardid");

        TVector<TString> shardRanges;
        for (ui32 shardId = 0; shardId < shards.size(); ++shardId) {
            ui32 shardMin = floor(65533.0 * shardId / shards.size());
            ui32 shardMax = floor(65533.0 * (shardId + 1) / shards.size()) - 1 + floor((shardId + 1.0) / shards.size());
            shardRanges.push_back(Join("-", shardMin, shardMax));
        }

        INFO_LOG << "Received from gencfg service info: replicas=" << replicas
            << " shards=" << JoinRange(",", shardRanges.begin(), shardRanges.end()) << Endl;
        if (shardsResult.empty()) {
            shardsResult = shardRanges;
        }
        if (!replicasResult) {
            replicasResult = replicas;
        }
    }

    TString GetSaasYtDelivery(const NSaasLB::ELogbrokerName logbroker, const TString& ytCluster) {
        return Join("-", "shared/saas", ytCluster, FormatLogbrokerName(logbroker));
    }
}

namespace NSaasLB {
    TServiceConfiguration::TServiceConfiguration(
        const TString& ns,
        const TString& name,
        const TString& ctype,
        bool noRemove /*= false*/,
        bool forceRemove /*= false*/,
        std::optional<TString> logbrokerToken
    )
        : Namespace(ns)
        , Name(name)
        , Ctype(ctype)
        , NoRemove(noRemove)
        , ForceRemove(forceRemove)
    {
        Init(logbrokerToken);
    }

    TServiceConfiguration::TServiceConfiguration(
        const TString& ns,
        const TString& name,
        const TString& ctype,
        std::optional<TServiceConfig> serviceConfig,
        std::optional<TVector<TString>> shards,
        std::optional<ui32> replicas,
        std::optional<TString> gencfgTag,
        bool noRemove /*= false*/,
        bool forceRemove /*= false*/,
        std::optional<TString> logbrokerToken
    )
        : Namespace(ns)
        , Name(name)
        , Ctype(ctype)
        , NoRemove(noRemove)
        , ForceRemove(forceRemove)
    {
        Init(logbrokerToken, serviceConfig, shards, replicas, gencfgTag);
    }

    void TServiceConfiguration::Init(
        std::optional<TString> logbrokerToken,
        std::optional<TServiceConfig> serviceConfig /*= {}*/,
        std::optional<TVector<TString>> shards /*= {}*/,
        std::optional<ui32> replicas /*= {}*/,
        std::optional<TString> gencfgTag /*= {}*/
    ) {
        Y_ENSURE(!NoRemove || !ForceRemove);

        ModifyManager.Reset(
            new TModifyManager(
                Namespace,
                Name,
                Ctype,
                logbrokerToken.value_or(GetLogbrokerToken()),
                GetLogbrokerRemoteMirrorRulesToken(),
                serviceConfig
            )
        );

        TNamespaceManager nsManager(Namespace);
        NamespaceConfig = nsManager.GetConfig();

        ServiceConfig = ModifyManager->GetServiceConfig();
        LoadServiceInfo(shards, replicas, gencfgTag);
    }

    inline TString TServiceConfiguration::GetTopicsPath(ELogbrokerKind kind) const {
        return kind == ELogbrokerKind::origin ? ServiceConfig.GetTopicsPath() : ServiceConfig.GetMirrorTopicsPath();
    }

    inline TString TServiceConfiguration::GetConsumersPath(ELogbrokerKind kind) const {
        return kind == ELogbrokerKind::origin ? ServiceConfig.GetConsumersPath() : ServiceConfig.GetMirrorConsumersPath();
    }

    void TServiceConfiguration::LoadServiceInfo(
        std::optional<TVector<TString>> shards,
        std::optional<ui32> replicas,
        std::optional<TString> gencfgTag
    ) {
        if (shards) {
            Shards = *shards;
        }
        if (replicas) {
            Replicas = *replicas;
        }
        if (shards && replicas) {
            return;
        }
        if (ServiceConfig.HasDeployManagerHost()) {
            FillInstancesInfoFromDM(
                ServiceConfig.GetDeployManagerHost(),
                Ctype,
                Name,
                Replicas,
                Shards
            );
        } else if (ServiceConfig.HasGencfg()){
            auto protoGroups = ServiceConfig.GetGencfg().GetGroups();
            TVector<TString> groups(protoGroups.begin(), protoGroups.end());
            FillInstancesInfoFromGencfg(groups, gencfgTag, Replicas, Shards);
        } else {
            ythrow yexception() << "Unknows source of shards/replicas information for service";
        }
        INFO_LOG << "Information about instances: replicas=" << Replicas
            << " shards=" << JoinRange(",", Shards.begin(), Shards.end()) << Endl;
    }

    TServiceDescription TServiceConfiguration::Describe() {
        TServiceDescription description = ModifyManager->Describe();
        for (auto& shard : Shards) {
            description.AddShards(shard);
        }
        description.SetReplicas(Replicas);
        return description;
    }

    TServiceChanges TServiceConfiguration::GetChanges() const {
        return ModifyManager->GetChanges();
    }

    void TServiceConfiguration::Apply() {
        ModifyManager->Apply();
    }

    void TServiceConfiguration::Create(const TVector<ui32>& writeTvm, const TVector<ui32>& readTvm) {
        bool authRequired = ServiceConfig.HasLogbrokerMirror();

        UpdateTopics(ELogbrokerKind::origin, authRequired);
        UpdateConsumers(ELogbrokerKind::origin);
        UpdateReadRules(ELogbrokerKind::origin);
        UpdateYtDelivery();

        GrantPermission(EServicePermission::write, writeTvm, ELogbrokerKind::origin);

        if (ServiceConfig.HasLogbrokerMirror()) {
            UpdateTopics(ELogbrokerKind::mirror, authRequired);
            UpdateConsumers(ELogbrokerKind::mirror);
            UpdateReadRules(ELogbrokerKind::mirror);
            UpdateRemoteMirrorRules();

            GrantPermission(EServicePermission::read, readTvm, ELogbrokerKind::mirror);
        } else {
            GrantPermission(EServicePermission::read, readTvm, ELogbrokerKind::origin);
        }

        UpdateLocks();
    }

    void TServiceConfiguration::Remove() {
        Y_VERIFY(!NoRemove);

        Shards.clear();
        Replicas = 0;

        UpdateTopics(ELogbrokerKind::origin);
        UpdateConsumers(ELogbrokerKind::origin);

        if (ServiceConfig.HasLogbrokerMirror()) {
            UpdateTopics(ELogbrokerKind::mirror);
            UpdateConsumers(ELogbrokerKind::mirror);
        }

        UpdateLocks();

        ModifyManager->RemoveServiceConfig();
    }

    void TServiceConfiguration::UpdateConsumers(ELogbrokerKind kind) {
        TVector<TString> consumers = ModifyManager->GetConsumers(GetConsumersPath(kind), kind);
        TVector<TString> targetConsumers = GetTargetConsumers(kind);

        TVector<TString> toCreate = GetEntitiesDifference(targetConsumers, consumers);
        TVector<TString> toRemove;
        for (const auto& entity : GetEntitiesDifference(consumers, targetConsumers)) {
            if (!IsInternalConsumer(entity)) {
                toRemove.push_back(entity);
            }
        }

        ModifyManager->CreateConsumers(toCreate, kind);
        if (!NoRemove) {
            ModifyManager->RemoveConsumers(toRemove, kind);
        }
    }

    void TServiceConfiguration::UpdateConsumers() {
        UpdateConsumers(ELogbrokerKind::origin);
        UpdateReadRules(ELogbrokerKind::origin);

        if (ServiceConfig.HasLogbrokerMirror()) {
            UpdateConsumers(ELogbrokerKind::mirror);
            UpdateReadRules(ELogbrokerKind::mirror);
        }

        UpdateLocks();
    }

    void TServiceConfiguration::UpdateTopics(ELogbrokerKind kind, bool authRequired) {
        TVector<TString> topics = ModifyManager->GetTopics(GetTopicsPath(kind), kind);
        TVector<TString> targetTopics = GetTargetTopics(kind);

        TVector<TString> toCreate = GetEntitiesDifference(targetTopics, topics);
        TVector<TString> toRemove = GetEntitiesDifference(topics, targetTopics);
        CreateTopics(toCreate, kind, authRequired);
        if (!NoRemove) {
            RemoveTopics(toRemove, kind);
        }
    }

    void TServiceConfiguration::UpdateTopics() {
        UpdateTopics(ELogbrokerKind::origin);
        UpdateReadRules(ELogbrokerKind::origin);
        UpdateYtDelivery();

        if (ServiceConfig.HasLogbrokerMirror()) {
            UpdateTopics(ELogbrokerKind::mirror);
            UpdateReadRules(ELogbrokerKind::mirror);
            UpdateRemoteMirrorRules();
        }
    }

    void TServiceConfiguration::UpdateLocks() {
        ELogbrokerKind kind = ServiceConfig.HasLogbrokerMirror() ? ELogbrokerKind::mirror : ELogbrokerKind::origin;
        TVector<TString> consumers = GetTargetConsumers(kind);
        UpdateLocks(consumers);
    }

    TMap<TString, EDataCenter> TServiceConfiguration::GetConsumerToDataCenter(const TVector<TString>& consumers) {
        TMap<TString, EDataCenter> consumerToDataCenter;
        for (auto dcIdx : ServiceConfig.GetDataCenters()) {
            auto dc = EDataCenter(dcIdx);
            TString dcName = ToString(dc);

            bool matchConsumer = false;
            for (auto&& consumer : consumers) {
                TVector<TString> nameParts;
                StringSplitter(consumer).Split('/').ParseInto(&nameParts);
                if (nameParts.back() == dcName) {
                    consumerToDataCenter[consumer] = dc;
                    matchConsumer = true;
                    break;
                }
            }

            if (!matchConsumer) {
                ythrow yexception() << "Unable to find a consumer for data center " << dcName;
            }
        }
        return consumerToDataCenter;
    }

    void TServiceConfiguration::RemoveReadRules(
        const TVector<TString>& topics,
        const TVector<TString>& consumers,
        ELogbrokerKind kind
    ) {
        TVector<TReadRule> toRemove;
        for (auto&& consumer : consumers) {
            for (auto&& topic : topics) {
                for (auto&& readRule : ModifyManager->GetReadRules(topic, kind)) {
                    if (consumer == readRule.GetConsumerPath()) {
                        toRemove.push_back(readRule);
                        break;
                    }
                }
            }
        }
        ModifyManager->RemoveReadRules(toRemove, kind);
    }

    void TServiceConfiguration::UpdateReadRules(ELogbrokerKind kind) {
        TVector<TString> topics = ModifyManager->GetTopics(GetTopicsPath(kind), kind);
        TVector<TString> consumers = ModifyManager->GetConsumers(GetConsumersPath(kind), kind);

        TVector<EDataCenter> dataCenters;
        if (ServiceConfig.HasLogbrokerMirror()) {
            dataCenters.push_back(EDataCenter::original);

            if (kind == ELogbrokerKind::origin) {
                auto consumerToDataCenter = GetConsumerToDataCenter(consumers);
                TVector<TString> requiredConsumers;
                for (const auto &pair : consumerToDataCenter) {
                    requiredConsumers.push_back(pair.first);
                }
                if (!NoRemove) {
                    auto consumersDiff = GetEntitiesDifference(consumers, requiredConsumers);
                    RemoveReadRules(topics, consumersDiff, kind);
                }
                consumers = requiredConsumers;
            }
        } else {
            for (auto dc : ServiceConfig.GetDataCenters()) {
                dataCenters.push_back(EDataCenter(dc));
            }
        }

        if (NamespaceConfig.HasDevConsumersPath()) {
            auto devConsumers = ModifyManager->GetConsumers(NamespaceConfig.GetDevConsumersPath(), kind);
            consumers.insert(consumers.end(), devConsumers.begin(), devConsumers.end());
        }

        TVector<TReadRule> toCreate;
        TVector<TReadRule> toRemove;
        for (auto&& topic : topics) {
            const auto& readRules = ModifyManager->GetReadRules(topic, kind);
            THashMap<TString, TVector<EDataCenter>> dataCentersByConsumer;
            for (auto&& rr : readRules) {
                dataCentersByConsumer[rr.GetConsumerPath()].push_back(rr.GetDataCenter());
            }
            for (auto&& consumer : consumers) {
                TVector<EDataCenter> currentDataCenters;
                auto it = dataCentersByConsumer.find(consumer);
                if (it != dataCentersByConsumer.end()) {
                    currentDataCenters = std::move(it->second);
                }

                for (auto dc : GetEntitiesDifference(dataCenters, currentDataCenters)) {
                    toCreate.emplace_back(topic, consumer, dc);
                }
                for (auto dc : GetEntitiesDifference(currentDataCenters, dataCenters)) {
                    toRemove.emplace_back(topic, consumer, dc);
                }
            }
        }

        ModifyManager->CreateReadRules(toCreate, kind);
        if (!NoRemove) {
            ModifyManager->RemoveReadRules(toRemove, kind);
        }
    }

    TMap<TString, TString> TServiceConfiguration::GetOriginToMirrorTopic(
        const TVector<TString>& originTopics,
        const TVector<TString>& mirrorTopics
    ) {
        Y_ENSURE(originTopics.size() == mirrorTopics.size(), "The same amount of origin and mirror topics required");

        TMap<TString, TString> result;
        for (const auto& originTopic : originTopics) {
            TVector<TString> originNameParts;
            StringSplitter(originTopic).Split('/').ParseInto(&originNameParts);

            bool matchTopic = false;
            for (const auto& mirrorTopic : mirrorTopics) {
                TVector<TString> mirrorNameParts;
                StringSplitter(mirrorTopic).Split('/').ParseInto(&mirrorNameParts);

                if (originNameParts.back() == mirrorNameParts.back()) {
                    result[originTopic] = mirrorTopic;
                    matchTopic = true;
                    break;
                }
            }

            if (!matchTopic) {
                ythrow yexception() << "Unable to find a mirror topic for " << originTopic;
            }
        }
        return result;
    }

    void TServiceConfiguration::UpdateRemoteMirrorRules() {
        ELogbrokerKind origin = ELogbrokerKind::origin;
        ELogbrokerName originLogbroker = ServiceConfig.GetLogbroker();
        TVector<TString> originTopics = ModifyManager->GetTopics(GetTopicsPath(origin), origin);
        TVector<TString> originConsumers = ModifyManager->GetConsumers(GetConsumersPath(origin), origin);

        ELogbrokerKind mirror = ELogbrokerKind::mirror;
        TVector<TString> mirrorTopics = ModifyManager->GetTopics(GetTopicsPath(mirror), mirror);

        TMap<TString, EDataCenter> consumerToDataCenter = GetConsumerToDataCenter(originConsumers);
        TMap<TString, TString> originToMirrorTopic = GetOriginToMirrorTopic(originTopics, mirrorTopics);

        TVector<TRemoteMirrorRule> toCreate;
        TVector<TRemoteMirrorRule> toRemove;
        for (const auto& [originTopic, mirrorTopic] : originToMirrorTopic) {
            TVector<TRemoteMirrorRule> expectedRules;
            for (const auto& pair : consumerToDataCenter) {
                expectedRules.emplace_back(mirrorTopic, pair.second, originTopic, pair.first, originLogbroker);
            }

            const auto& actualRules = ModifyManager->GetRemoteMirrorRules(mirrorTopic);
            for (const auto& rule : GetEntitiesDifference(expectedRules, actualRules)) {
                toCreate.push_back(rule);
            }
            for (const auto& rule : GetEntitiesDifference(actualRules, expectedRules)) {
                toRemove.push_back(rule);
            }
        }

        ModifyManager->RemoveRemoteMirrorRules(toRemove);
        ModifyManager->CreateRemoteMirrorRules(toCreate);
    }

    void TServiceConfiguration::UpdateLocks(const TVector<TString>& consumers) {
        TVector<TString> locks = ModifyManager->GetLocks(ServiceConfig.GetLocksPath());

        TVector<TString> toCreate = GetEntitiesDifference(consumers, locks);
        TVector<TString> toRemove = GetEntitiesDifference(locks, consumers);
        ModifyManager->AddLocks(ServiceConfig.GetLocksPath(), toCreate);
        if (!NoRemove) {
            ModifyManager->RemoveLocks(ServiceConfig.GetLocksPath(), toRemove);
        }
    }

    void TServiceConfiguration::UpdateYtDelivery() {
        TVector<TString> targetYtDeliveries;
        for (auto& ytCluster : ServiceConfig.GetYtDeliveryCluster()) {
            targetYtDeliveries.push_back(GetSaasYtDelivery(ServiceConfig.GetLogbroker(), ytCluster));
        }

        TVector<TString> topics = ModifyManager->GetTopics(
            GetTopicsPath(ELogbrokerKind::origin),
            ELogbrokerKind::origin
        );

        for (auto& topic : topics) {
            auto deliveries = ModifyManager->GetSaasYtDeliveries(topic, ELogbrokerKind::origin);
            TVector<TString> toCreate = GetEntitiesDifference(targetYtDeliveries, deliveries);
            TVector<TString> toRemove = GetEntitiesDifference(deliveries, targetYtDeliveries);
            for (auto& delivery : toCreate) {
                ModifyManager->AddYtDelivery(topic, delivery, ELogbrokerKind::origin);
            }
            if (!NoRemove) {
                for (auto& delivery : toRemove) {
                    ModifyManager->RemoveYtDelivery(topic, delivery, ELogbrokerKind::origin);
                }
            }
        }

        if (ServiceConfig.HasLogbrokerMirror()) {
            TVector<TString> mirrorTopics = ModifyManager->GetTopics(
                GetTopicsPath(ELogbrokerKind::mirror),
                ELogbrokerKind::mirror
            );

            for (auto& topic : mirrorTopics) {
                auto deliveries = ModifyManager->GetSaasYtDeliveries(topic, ELogbrokerKind::mirror);
                if (!NoRemove) {
                    for (auto& delivery : deliveries) {
                        ModifyManager->RemoveYtDelivery(topic, delivery, ELogbrokerKind::mirror);
                    }
                }
            }
        }
    }

    void TServiceConfiguration::CreateTopics(const TVector<TString>& topics, ELogbrokerKind kind, bool authRequired) {
        ModifyManager->CreateTopics(topics, kind, authRequired);
    }

    void TServiceConfiguration::RemoveTopics(const TVector<TString>& topics, ELogbrokerKind kind) {
        if (NoRemove) {
            return;
        }
        if (!UsedOnlyByThisService(topics, kind) && !ForceRemove) {
            return;
        }
        ModifyManager->RemoveTopics(topics, kind);
    }

    void TServiceConfiguration::GrantPermission(EServicePermission permission, const TVector<ui32>& tvm, ELogbrokerKind kind) {
        TVector<TString> subjects = TvmToSubjects(tvm);
        switch (permission) {
            case EServicePermission::read: {
                ModifyManager->GrantPermissions(GetConsumersPath(kind), {EPermission::ReadAsConsumer}, subjects, kind);
                ModifyManager->GrantPermissions(GetTopicsPath(kind), {EPermission::ReadTopic}, subjects, kind);
                break;
            }
            case EServicePermission::write: {
                ModifyManager->GrantPermissions(GetTopicsPath(kind), {EPermission::WriteTopic}, subjects, kind);
                break;
            }
            default: {
                ythrow yexception() << "Unknown service permission to grant: " << permission;
            }
        }
    }

    void TServiceConfiguration::GrantPermission(EServicePermission permission, const TVector<ui32>& tvm) {
        if (permission == EServicePermission::write) {
            GrantPermission(permission, tvm, ELogbrokerKind::origin);
        } else {
            ELogbrokerKind kind = ServiceConfig.HasLogbrokerMirror() ? ELogbrokerKind::mirror : ELogbrokerKind::origin;
            GrantPermission(permission, tvm, kind);
        }
    }

    void TServiceConfiguration::RevokePermission(EServicePermission permission, const TVector<ui32>& tvm, ELogbrokerKind kind) {
        TVector<TString> subjects = TvmToSubjects(tvm);
        switch (permission) {
            case EServicePermission::read: {
                ModifyManager->RevokePermissions(GetConsumersPath(kind), {EPermission::ReadAsConsumer}, subjects, kind);
                ModifyManager->RevokePermissions(GetTopicsPath(kind), {EPermission::ReadTopic}, subjects, kind);
                break;
            }
            case EServicePermission::write: {
                ModifyManager->RevokePermissions(GetTopicsPath(kind), {EPermission::WriteTopic}, subjects, kind);
                break;
            }
            default: {
                ythrow yexception() << "Unknown service permission to revoke: " << permission;
            }
        }
    }

    void TServiceConfiguration::RevokePermission(EServicePermission permission, const TVector<ui32>& tvm) {
        if (permission == EServicePermission::write) {
            RevokePermission(permission, tvm, ELogbrokerKind::origin);
        } else {
            ELogbrokerKind kind = ServiceConfig.HasLogbrokerMirror() ? ELogbrokerKind::mirror : ELogbrokerKind::origin;
            RevokePermission(permission, tvm, kind);
        }
    }

    TVector<TString> TServiceConfiguration::GetTargetTopics(ELogbrokerKind kind) const {
        TVector<TString> topics;
        for (auto&& shard : Shards) {
            const TString& path = Join("/", GetTopicsPath(kind), Join("-", "shard", shard));
            topics.push_back(GetCorrectEntityPath(path));
        }
        return topics;
    }

    TVector<TString> TServiceConfiguration::GetTargetConsumers(ELogbrokerKind kind) const {
        TVector<TString> consumers;
        if (!ServiceConfig.HasLogbrokerMirror() || kind == ELogbrokerKind::mirror) {
            for (ui32 i = 0; i < Replicas; ++i) {
                consumers.push_back(GetCorrectEntityPath((Join("/", GetConsumersPath(kind), i))));
            }
        } else {
            for (auto dc : ServiceConfig.GetDataCenters()) {
                consumers.push_back(GetCorrectEntityPath((Join("/", GetConsumersPath(kind), EDataCenter_Name(dc)))));
            }
        }
        return consumers;
    }

    bool TServiceConfiguration::UsedOnlyByThisService(const TVector<TString>& topics, ELogbrokerKind kind) {
        for (auto&& topic : topics) {
            if (!UsedOnlyByThisService(topic, kind)) {
                return false;
            }
        }
        return true;
    }

    bool TServiceConfiguration::UsedOnlyByThisService(const TString& topic, ELogbrokerKind kind) {
        TVector<TString> serviceConsumers = ModifyManager->GetConsumers(GetConsumersPath(kind), kind);
        THashSet<TString> serviceConsumersSet(serviceConsumers.begin(), serviceConsumers.end());
        TVector<TString> topicConsumers = ModifyManager->GetConsumersFor(topic, kind);

        THashSet<TString> devConsumers;
        if (NamespaceConfig.HasDevConsumersPath()) {
            auto consumers = ModifyManager->GetConsumers(NamespaceConfig.GetDevConsumersPath(), kind);
            devConsumers = {consumers.begin(), consumers.end()};
        }
        for (auto&& consumer : topicConsumers) {
            if (!serviceConsumersSet.count(consumer) && !devConsumers.count(consumer) && !IsInternalConsumer(consumer)) {
                Cout << "Topics won't be deleted, because topic (" << topic << ") is read by the consumer not from this service: " << consumer << Endl;
                return false;
            }
        }
        return true;
    }

    bool TServiceConfiguration::IsInternalConsumer(const TString& consumer) {
        // YtDelivery & mirror consumers
        return consumer.find("shared/") == 0 || consumer.find("saas/shared/") == 0;
    }
}
