#include "commands.h"
#include "human_readable_output.h"

#include <saas/library/persqueue/configuration/namespace/namespace.h>


namespace NSaasLB {
    IServiceCommand::IServiceCommand(
        const TOptionsType& options,
        std::optional<TServiceConfig> serviceConfig,
        std::optional<TVector<TString>> shards,
        std::optional<ui32> replicas,
        std::optional<TString> gencfgTag,
        bool noRemove /*= true*/,
        bool forceRemove /*= false*/
    )
        : ServiceConfiguration(
            MakeHolder<NSaasLB::TServiceConfiguration>(
                options.GetNamespace(),
                options.GetService(),
                options.GetCtype(),
                serviceConfig,
                shards,
                replicas,
                gencfgTag,
                noRemove,
                forceRemove
            )
        )
    {}

    IModifyCommand::IModifyCommand(const TOptionsType& options)
        : DryRun(options.GetDryRun())
        , SkipConfirm(options.GetSkipConfirm())
    {
    }

    void IModifyCommand::Execute() {
        DoExecute();
        PrintChanges();
        if (!DryRun && UserConfirm()) {
            Apply();
        }
    }

    bool IModifyCommand::UserConfirm() const {
        if (SkipConfirm) {
            return true;
        }
        Cout << "Apply changes (y/n): ";
        TString apply;
        Cin >> apply;
        return apply == "y";
    }

    void IModifyCommand::Apply() {
        DoApply();
        Cout << "Successfully applied." << Endl;
    }

    INamespaceCommand::INamespaceCommand(const TOptionsType&, const TString& name)
        : NamespaceManager(name)
    {}

    INamespaceCommand::INamespaceCommand(const TOptionsType&)
    {}

    IUpdateNamespaceCommand::IUpdateNamespaceCommand(const TOptionsType& options, const TString& name)
        : INamespaceCommand(options, name)
        , IModifyCommand(options)
    {}

    IUpdateNamespaceCommand::IUpdateNamespaceCommand(const TOptionsType& options)
        : INamespaceCommand(options)
        , IModifyCommand(options)
    {}

    TDescribeNamespaceCommand::TDescribeNamespaceCommand(const TOptionsType& options)
        : INamespaceCommand(options, options.GetName())
    {}

    void TDescribeNamespaceCommand::Execute() {
        auto description = NamespaceManager.Describe();
        DescribeNamespace(description);
    }

    void IUpdateNamespaceCommand::PrintChanges() const {
        auto request = NamespaceManager.GetChanges();
        INFO_LOG << "Namespace changes: " << Endl << request << Endl;
        if (request.GetAction() == EChangeAction::REMOVE) {
            Cout << "Namespce '" << request.GetName() << "' will be removed." << Endl;
        } else {
            if (request.GetAction() == EChangeAction::CREATE) {
                Cout << "Namespace '" << request.GetName() << "' will be created with following config:" << Endl;
            } else {
                Cout << "New config will be set for namespace '" << request.GetName() << "'.  New config is follow:" << Endl;
            }
            DescribeNamespaceConfig(request.GetConfig());
        }
    }

    void IUpdateNamespaceCommand::DoApply() {
        NamespaceManager.Apply();
    }

    TRemoveNamespaceCommand::TRemoveNamespaceCommand(const TOptionsType& options)
        : IUpdateNamespaceCommand(options, options.GetName())
    {}

    void TRemoveNamespaceCommand::DoExecute() {
        NamespaceManager.Remove();
    }

    TCreateNamespaceCommand::TCreateNamespaceCommand(const TOptionsType& options)
        : IUpdateNamespaceCommand(options)
        , Name(options.GetName())
        , Config(CreateConfig(options))
    {}

    TCreateNamespaceCommand::TCreateNamespaceCommand(const TOptionsType& options, const TString& name)
        : IUpdateNamespaceCommand(options, name)
        , Config(CreateConfig(options))
    {}

    void TCreateNamespaceCommand::DoExecute() {
        Y_VERIFY(Name);
        NamespaceManager.Create(Name, Config);
    }

    TNamespaceConfig TCreateNamespaceCommand::CreateConfig(const TOptionsType& options) {
        TNamespaceConfig config;

        config.SetLogbrokerServicesPath(options.GetLogbrokerServicesPath());
        config.SetLocksPath(options.GetLocksPath());
        config.SetLocksServer(options.GetLocksServer());
        if (options.GetServicesConfigsPath()) {
            config.SetServicesConfigsPath(options.GetServicesConfigsPath().value());
        } else {
            config.SetServicesConfigsPath(TNamespaceManager::GetNamespaceConfigPath(options.GetName()));
        }
        if (options.GetDeployManagerHost()) {
            config.SetDeployManagerHost(options.GetDeployManagerHost().value());
        }
        if (options.GetDevConsumersPath()) {
            config.SetDevConsumersPath(options.GetDevConsumersPath().value());
        }
        return config;
    }

    TUpdateNamespaceCommand::TUpdateNamespaceCommand(const TOptionsType& options)
        : TCreateNamespaceCommand(options, options.GetName())
    {}

    void TUpdateNamespaceCommand::DoExecute() {
        NamespaceManager.Modify(Config);
    }

    IModifyServiceCommand::IModifyServiceCommand(
        const TOptionsType& options,
        std::optional<TServiceConfig> serviceConfig,
        std::optional<TVector<TString>> shards,
        std::optional<ui32> replicas
    )
        : IServiceCommand(options, serviceConfig, shards, replicas, options.GetGencfgTag(), options.GetNoRemove(), options.GetForceRemove())
        , IModifyCommand(options)
    {
    }

    void IModifyServiceCommand::PrintChanges() const {
        const auto& changes = ServiceConfiguration->GetChanges();
        INFO_LOG << "Service changes:" << Endl << changes.DebugString() << Endl;
        PrintServiceChanges(changes);
    }

    void IModifyServiceCommand::DoApply() {
        ServiceConfiguration->Apply();
    }

    TUpdateTopicsCommand::TUpdateTopicsCommand(const TOptionsType& options)
        : IModifyServiceCommand(options, {}, options.GetShards(), {})
    {}

    void TUpdateTopicsCommand::DoExecute() {
        ServiceConfiguration->UpdateTopics();
    }

    TUpdateConsumersCommand::TUpdateConsumersCommand(const TOptionsType& options)
        : IModifyServiceCommand(options, {}, {}, options.GetReplicas())
    {}

    void TUpdateConsumersCommand::DoExecute() {
        ServiceConfiguration->UpdateConsumers();
    }

    void TUpdateLocksCommand::DoExecute() {
        ServiceConfiguration->UpdateLocks();
    }

    TCreateServiceCommand::TCreateServiceCommand(const TOptionsType& options)
        : IModifyServiceCommand(options, CreateServiceConfig(options), options.GetShards(), options.GetReplicas())
        , TvmWrite(options.GetTvmWrite())
        , TvmRead(options.GetTvmRead())
    {}

    void TCreateServiceCommand::DoExecute() {
        ServiceConfiguration->Create(TvmWrite, TvmRead);
    }

    TServiceConfig TCreateServiceCommand::CreateServiceConfig(const TOptionsType& options) const {
        TNamespaceManager nsManager(options.GetNamespace());
        auto nsConfig = nsManager.GetConfig();
        return nsManager.CreateServiceConfig(
            options.GetService(),
            options.GetCtype(),
            options.GetLogbrokerName(),
            options.GetDataCenters(),
            options.GetYtDeliveryClusters(),
            options.GetLogbrokerMirrorName(),
            options.GetTopicsPath(),
            options.GetMirrorTopicsPath(),
            options.GetConsumersPath(),
            options.GetMirrorConsumersPath(),
            options.GetLocksPath(),
            options.GetDeployManagerHost(),
            options.GetGencfgGroups()
        );
    }

    TRemoveServiceCommand::TRemoveServiceCommand(const TOptionsType& options)
        : IModifyServiceCommand(options)
    {}

    void TRemoveServiceCommand::DoExecute() {
        ServiceConfiguration->Remove();
    }

    TDescribeServiceCommand::TDescribeServiceCommand(const TOptionsType& options)
        : IServiceCommand(options)
        , ShowLocks(options.GetShowLocks())
    {}

    void TDescribeServiceCommand::Execute() {
        auto description = ServiceConfiguration->Describe();

        DescribeServiceConfig(Cout, description.GetConfig());

        auto showCount = [](const TString& title, ui32 count, ui32 target = 0) {
            TStringStream s;
            s << title << "\r\t\t\t" << count;
            if (target && count < target) {
                s << "\t is less than " << target << "!";
            }
            if (target && count > target) {
                s << "\t is more than " << target << "!";
            }
            Cout << s.Str() << Endl;
        };

        bool hasMirror = description.GetConfig().HasLogbrokerMirror();
        size_t consumersCount = hasMirror ? description.MirrorConsumersSize() : description.ConsumersSize();

        Cout << Endl << "Resources count" << Endl;

        showCount("Shards: ", description.ShardsSize());
        showCount("Topics: ", description.TopicsSize(), description.ShardsSize());
        if (hasMirror) {
            showCount("Mirror Topics: ", description.MirrorTopicsSize(), description.ShardsSize());
        }
        showCount("Replicas: ", description.GetReplicas());
        showCount("Consumers: ", consumersCount, description.GetReplicas());
        showCount("Locks:  ", description.LocksSize(), consumersCount);

        if (ShowLocks) {
            Cout << Endl << "Locks:" << Endl;
            for (ui32 i = 0; i < description.LocksSize(); ++i) {
                Cout << "\t" << i << ". " << description.GetLocks(i).GetLock().GetResource() << Endl;
                Cout << "\t\tzk_node: " << description.GetLocks(i).GetLock().GetZooKeeperLock().GetNodePath() << Endl;
                Cout << "\t\tLocked topics: " << description.GetLocks(i).LockedTopicsSize() << Endl;
                for (auto& locked : description.GetLocks(i).GetLockedTopics()) {
                    Cout << "\t\t\t - " << locked.GetLockedBy() << " " << locked.GetTopic() << Endl;
                }
            }
        }
    }
}
