#include "namespace.h"

#include <util/string/vector.h>
#include <util/string/join.h>
#include <util/folder/path.h>

namespace NSaasLB {
    TNamespaceManager::TNamespaceManager(const TString& ns)
        : Namespace(ns)
        , NamespaceConfigPath(GetNamespaceConfigPath(ns))
    {
        InitStorage();
        LoadConfig();
    }

    TNamespaceManager::TNamespaceManager() {
        InitStorage();
    }

    void TNamespaceManager::InitStorage() {
        NZooKeeper::TZooKeeperOptions zkOptions(MAIN_SAAS_ZK_CLUSTER);
        Storage = MakeHolder<NZooKeeper::TZooKeeper>(zkOptions);
    }

    void TNamespaceManager::Create(const TString& name, const TNamespaceConfig& config) {
        Y_VERIFY(!ChangeRequest, "Namespace already changed");
        Y_VERIFY(!Config, "Namespace already created");
        Namespace = name;
        NamespaceConfigPath = GetNamespaceConfigPath(Namespace);
        ChangeRequest = TNamespaceModifyRequest();
        ChangeRequest->SetAction(EChangeAction::CREATE);
        ChangeRequest->SetName(Namespace);
        ChangeRequest->MutableConfig()->CopyFrom(config);
        Config = config;
    }

    void TNamespaceManager::Modify(const TNamespaceConfig& config) {
        Y_VERIFY(!ChangeRequest, "Namespace already changed");
        Y_VERIFY(Config, "Namespace not created");
        ChangeRequest = TNamespaceModifyRequest();
        ChangeRequest->SetAction(EChangeAction::MODIFY);
        ChangeRequest->SetName(Namespace);
        ChangeRequest->MutableConfig()->CopyFrom(config);
        Config = config;
    }

    void TNamespaceManager::Remove() {
        Y_VERIFY(!ChangeRequest, "Namespace already changed");
        Y_VERIFY(Config, "Namespace not created");
        ChangeRequest = TNamespaceModifyRequest();
        ChangeRequest->SetAction(EChangeAction::REMOVE);
        ChangeRequest->SetName(Namespace);
        Config = {};
    }

    void TNamespaceManager::Apply() {
        if (!ChangeRequest) {
            return;
        }
        if (ChangeRequest->GetAction() == EChangeAction::REMOVE) {
            RemoveConfig();
        } else {
            if (ChangeRequest->GetAction() == EChangeAction::CREATE && Storage->Exists(NamespaceConfigPath)) {
                ythrow yexception() << "namespace alredy exists";
            }
            SaveConfig();
        }
    }

    const TString& TNamespaceManager::GetName() const {
        Y_VERIFY(!Namespace.empty());
        return Namespace;
    }

    const TNamespaceConfig& TNamespaceManager::GetConfig() const {
        Y_VERIFY(Config);
        return Config.value();
    }

    TNamespaceDescription TNamespaceManager::Describe() const {
        TNamespaceDescription description;
        description.SetName(GetName());
        description.MutableConfig()->CopyFrom(GetConfig());

        if (Storage->Exists(Config->GetServicesConfigsPath())) {
            auto services = Storage->GetChildren(Config->GetServicesConfigsPath());
            for (auto& s : services) {
                auto serviceInfo = description.AddServices();
                serviceInfo->SetName(s);
                TString servicePath = Config->GetServicesConfigsPath() + "/" + s;
                auto ctypes = Storage->GetChildren(servicePath);
                *serviceInfo->MutableCtypes() = {ctypes.begin(), ctypes.end()};
            }
        }
        return description;
    }

    TNamespaceModifyRequest TNamespaceManager::GetChanges() const {
        Y_VERIFY(ChangeRequest);
        return ChangeRequest.value();
    }

    TServiceConfig TNamespaceManager::CreateServiceConfig(
        const TString& serviceName,
        const TString& ctype,
        ELogbrokerName logbroker,
        const TVector<EDataCenter>& dataCenters,
        const TVector<TString>& ytDeliveryClusters,
        std::optional<ELogbrokerName> logbrokerMirror,
        std::optional<TString> topicsPath,
        std::optional<TString> mirrorTopicsPath,
        std::optional<TString> consumersPath,
        std::optional<TString> mirrorConsumersPath,
        std::optional<TString> locksPath,
        std::optional<TString> deployManagerHost,
        std::optional<TVector<TString>> gencfgGroups
    ) const {
        Y_VERIFY(Config);
        TServiceConfig config;
        config.SetLogbroker(logbroker);
        *config.MutableDataCenters() = {dataCenters.begin(), dataCenters.end()};

        TString servicePath = Join("/", Config->GetLogbrokerServicesPath(), serviceName, ctype);
        config.SetTopicsPath(topicsPath.value_or(servicePath + "/topics"));

        TString serviceParentPath = TFsPath(Config->GetLogbrokerServicesPath()).Parent();
        TString defaultPath = logbrokerMirror ? serviceParentPath + "/shared/mirror-consumers" : servicePath + "/consumers";
        config.SetConsumersPath(consumersPath.value_or(defaultPath));

        config.SetLocksServer(Config->GetLocksServer());
        config.SetLocksPath(locksPath.value_or(Join("/", Config->GetLocksPath(), serviceName, ctype)));

        *config.MutableYtDeliveryCluster() = {ytDeliveryClusters.begin(), ytDeliveryClusters.end()};

        if (deployManagerHost) {
            config.SetDeployManagerHost(deployManagerHost.value());
        } else if (gencfgGroups) {
            *config.MutableGencfg()->MutableGroups() = {gencfgGroups->begin(), gencfgGroups->end()};
        } else if (Config->HasDeployManagerHost()) {
            config.SetDeployManagerHost(Config->GetDeployManagerHost());
        } else {
            ythrow yexception() << "You must specify gencfg groups list or deploy_manager host";
        }

        if (logbrokerMirror) {
            config.SetLogbrokerMirror(logbrokerMirror.value());
            config.SetMirrorTopicsPath(mirrorTopicsPath.value_or(config.GetTopicsPath()));
            config.SetMirrorConsumersPath(mirrorConsumersPath.value_or(servicePath + "/consumers"));
        }

        return config;
    }

    void TNamespaceManager::LoadConfig() {
        try {
            TString namespaceConfigData;
            try {
                namespaceConfigData = Storage->GetData(NamespaceConfigPath);
            } catch (NZooKeeper::TNodeNotExistsError&) {
                ythrow yexception() << "no namespace config by path=" << NamespaceConfigPath;
            }
            TNamespaceConfig config;
            if (!config.ParseFromString(namespaceConfigData)) {
                ythrow yexception() << "incorrect namespace config by path=" << NamespaceConfigPath;
            }
            Config = config;
        } catch(...) {
            ythrow yexception() << "Can not get namespace config from ZK: " << CurrentExceptionMessage();
        }
    }

    TString TNamespaceManager::GetNamespaceConfigPath(const TString& name) {
        return "/logbroker/namespaces/" + name;
    }

    void TNamespaceManager::SaveConfig() {
        TString serializedConfig;
        Y_PROTOBUF_SUPPRESS_NODISCARD Config->SerializeToString(&serializedConfig);

        auto dirs = SplitString(NamespaceConfigPath, "/");
        TString prefix = "";
        for (auto&& dir : dirs) {
            prefix += "/" + dir;
            if (!Storage->Exists(prefix)) {
                Storage->Create(prefix, "", NZooKeeper::ACLS_ALL, NZooKeeper::CM_PERSISTENT);
            }
        }
        Storage->SetData(NamespaceConfigPath, serializedConfig);
    }

    void TNamespaceManager::RemoveConfig() {
        Storage->Delete(NamespaceConfigPath, -1);
    }
}
