#include "task.h"
#include <saas/deploy_manager/scripts/add_slots/action.h>
#include <saas/deploy_manager/scripts/aggregate/action.h>
#include <saas/deploy_manager/scripts/allocate_slots/action/action_same.h>
#include <saas/deploy_manager/scripts/common/dead_slots_collector/dead_slots_collector.h>
#include <saas/deploy_manager/scripts/deploy/action.h>
#include <saas/deploy_manager/scripts/logic/action.h>
#include <saas/deploy_manager/scripts/release_slots/action.h>
#include <saas/deploy_manager/scripts/restore_index/action.h>
#include <saas/deploy_manager/scripts/searchmap/action.h>
#include <saas/deploy_manager/meta/cluster.h>
#include <saas/deploy_manager/modules/resources/resources.h>
#include <saas/library/daemon_base/protos/status.pb.h>
#include <util/string/split.h>

namespace NRTYDeploy {

    std::pair<TString, TInterval<NSearchMapParser::TShardIndex>> ParseAbsentSlot(const TString& slot) {
        TVector<TString> split;
        split.reserve(3);
        StringSplitter(slot).Split('_').AddTo(&split);
        if (split.size() != 3)
            ythrow yexception() << "Invalid absent host " << slot;

        TVector<TStringBuf> splitInterval;
        splitInterval.reserve(2);
        StringSplitter(split[1]).Split('-').AddTo(&splitInterval);
        NSearchMapParser::TShardIndex shardMin;
        NSearchMapParser::TShardIndex shardMax;
        if (splitInterval.size() != 2 || !TryFromString(splitInterval[0], shardMin)|| !TryFromString(splitInterval[1], shardMax))
            ythrow yexception() << "Invalid absent host interval" << splitInterval[1];
        return std::make_pair<TString, TInterval<NSearchMapParser::TShardIndex>>(std::move(split[0]), TInterval<NSearchMapParser::TShardIndex>(shardMin, shardMax));
    }

    bool TClusterControlTask::OnService(const NSearchMapParser::TSearchMapService& info) {
        if (Service == info.Name || !Service) {
            SlotsForReplace[info.Name];
            SlotsForRestore[info.Name];
            return true;
        } else {
            return false;
        }
    }

    void TClusterControlTask::OnHost(const NSearchMapParser::TSearchMapHost& host, const NSearchMapParser::TSearchMapReplica& /*replica*/, const NSearchMapParser::TSearchMapService& service) {
        const NRTYCluster::TSlotData sd(host.Name, host.SearchPort);
        if (!CTypeCluster.IsExists(NRTYCluster::TSlotData(host.GetShortHostName(), host.SearchPort))) {
            SlotsForReplace[service.Name].AddInfo(sd.GetDC(), host.Shards);
            SlotsForRelease[service.Name].push_back(host.GetSlotName());
        } else {
            IControllersChecker::TSlotInfo slotInfo = CommonData->GetControllersChecker().CheckSlot(sd);
            if (!slotInfo.IsNormalSlot) {
                DEBUG_LOG << "Slot " << host.GetSlotName() << " detected as not normal" << Endl;
                SlotsNotUsefulForRestoreSource.insert(host.GetSlotName());
                if (slotInfo.LoadedFromStorage) {
                    if (slotInfo.ControllerStatus.Status == NController::TSlotStatus::FailedIndex) {
                        CHECK_WITH_LOG(SlotsForRestore[service.Name].Add(host));
                    } else {
                        SlotsForReplace[service.Name].AddInfo(sd.GetDC(), host.Shards);
                    }
                }
            }
        }
    }

    TClusterControlTask::TClusterControlTask(const TClusterTask::TCgiContext& context, NRTYDeploy::ICommonData* commonData,
        const TVector<TString>& predefinedSlotsPoolRestore,
        const TVector<TString>& predefinedSlotsPoolReplace,
        const TVector<TString>& copySlotsPool,
        const TVector<TString>& newSlotsPool,
        const TVector<TString>& predefinedSlotsPoolReplaceAbsent,
        bool deployProxies,
        const float mayBeDeadIntervalPercentage)
        : TClusterTask(context, commonData, CLUSTER_CONTROL_TASK_TYPE)
        , PredefinedSlotsPoolRestore(predefinedSlotsPoolRestore)
        , PredefinedSlotsPoolReplace(predefinedSlotsPoolReplace)
        , PredefinedSlotsPoolReplaceAbsent(predefinedSlotsPoolReplaceAbsent)
        , NewSlotsPool(newSlotsPool)
        , PredefinedSlotsPoolCopy(copySlotsPool)
        , DeployProxies(deployProxies)
        , MayBeDeadIntervalPercentage(mayBeDeadIntervalPercentage)
    {
    }

    void TClusterControlTask::DoBuildTask() {
        try {
            const NSaas::TClusterConst* clusterPtr = NSaas::TClusterLocker::DefineConst(*CommonData, CType);
            if (!clusterPtr) {
                ythrow yexception() << "Incorrect cluster info for " << CType;
            }
            const NSearchMapParser::TSearchMap& sm = clusterPtr->RTYSearchMap();

            NRTYCluster::TCluster clusterHard = CommonData->GetResourcesManager().GetCluster(RTYSERVER_SERVICE, Service);
            auto cTypeCluster = clusterHard.GetCTypeCluster();
            if (!cTypeCluster.contains(CType)) {
                ythrow yexception() << "Incorrect cluster-hard info for ctype " << CType;
            }

            CTypeCluster = cTypeCluster.find(CType)->second;

            if (PredefinedSlotsPoolRestore.size() || PredefinedSlotsPoolReplace.size() || PredefinedSlotsPoolCopy.size() || PredefinedSlotsPoolReplaceAbsent.size()) {
                SlotsNotUsefulForRestoreSource = TDeadSlotsCollector::GetNotUsefulSlots(Service, CommonData, sm);
                auto* serviceInfo = sm.GetService(Service);
                if (!Service || !serviceInfo) {
                    ythrow yexception() << "Incorrect service for predefined slots";
                }
                SlotsForReplace[Service];
                SlotsForRestore[Service];
                for (auto&& i : PredefinedSlotsPoolRestore) {
                    NSearchMapParser::TSearchMapHost host;
                    if (!serviceInfo->GetHostBySlot(i, host)) {
                        ythrow yexception() << "Incorrect slot in list PredefinedSlotsPoolRestore: " << i;
                    }
                    SlotsForRestore[Service].Add(host);
                    SlotsNotUsefulForRestoreSource.insert(host.GetSlotName());
                }
                for (auto&& i : PredefinedSlotsPoolReplace) {
                    NSearchMapParser::TSearchMapHost host;
                    if (!serviceInfo->GetHostBySlot(i, host)) {
                        ythrow yexception() << "Incorrect slot in list PredefinedSlotsPoolReplace: " << i;
                    }
                    NRTYCluster::TSlotData sd(host.Name, host.SearchPort);
                    SlotsForReplace[Service].AddInfo(sd.GetDC(), host.Shards);
                    SlotsForRelease[Service].push_back(host.GetSlotName());
                    SlotsNotUsefulForRestoreSource.insert(host.GetSlotName());
                }
                for (auto&& i : PredefinedSlotsPoolReplaceAbsent) {
                    const std::pair<TString, TInterval<NSearchMapParser::TShardIndex>> parsedAbsentSlot = ParseAbsentSlot(i);
                    SlotsForReplace[Service].AddInfo(parsedAbsentSlot.first, parsedAbsentSlot.second);
                }
                for (auto&& i : PredefinedSlotsPoolCopy) {
                    NSearchMapParser::TSearchMapHost host;
                    if (!serviceInfo->GetHostBySlot(i, host)) {
                        ythrow yexception() << "Incorrect slot in list PredefinedSlotsPoolCopy: " << i;
                    }
                    NRTYCluster::TSlotData sd(host.Name, host.SearchPort);
                    SlotsForReplace[Service].AddInfo(sd.GetDC(), host.Shards);
                }
            } else {
                sm.Scan(*this);
            }

            CHECK_WITH_LOG(SlotsForRestore.size() == SlotsForReplace.size());

            NDaemonController::TAction::TPtr actionBegin = new NRTYDeploy::TLogicAction();
            NRTYScript::TSlotTaskContainer startNode = GetScript()->AddAction(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(), CommonData->GetDeployManagerBalanserUriPrefix(), actionBegin);

            NRTYScript::TContainersPool front;
            for (auto&& i : SlotsForRestore) {
                if (i.second.Size()) {
                    NDaemonController::TAction::TPtr disableSlots = new NDaemonController::TSimpleSearchmapModifAction(i.second.Serialize().GetStringRobust(), CType, i.first, "disable_search,disable_indexing", ServiceType, true, false);
                    front.Add(startNode.AddNext(*GetScript(), disableSlots));
                }
            }

            NRTYScript::TSlotTaskContainer nodeStartRestoring = startNode;
            if (!front.IsEmpty()) {
                NRTYScript::TSlotTaskContainer nodeModifiedReady = front.AddNext(*GetScript(), new NRTYDeploy::TLogicAction());
                if (!DeployProxies) {
                    nodeStartRestoring = nodeModifiedReady;
                } else {
                    NRTYScript::TContainersPool disabledState;
                    {
                        auto currentSPDeploy = nodeModifiedReady.AddNext(*GetScript(), new NRTYDeploy::TDeployAction(SEARCH_PROXY_SERVICE, CType, "NEW",
                            NDaemonController::apStart, SEARCH_PROXY_SERVICE, MayBeDeadIntervalPercentage));
                        disabledState.Add(currentSPDeploy.AddNext(*GetScript(), new NRTYDeploy::TWaitAsyncAction(currentSPDeploy.GetName())));

                        auto currentIPDeploy = nodeModifiedReady.AddNext(*GetScript(), new NRTYDeploy::TDeployAction(INDEXER_PROXY_SERVICE, CType, "NEW",
                            NDaemonController::apStart, INDEXER_PROXY_SERVICE, MayBeDeadIntervalPercentage));
                        disabledState.Add(currentIPDeploy.AddNext(*GetScript(), new NRTYDeploy::TWaitAsyncAction(currentIPDeploy.GetName())));
                    }
                    nodeStartRestoring = disabledState.AddNext(*GetScript(), new NRTYDeploy::TLogicAction());
                }
            }

            NRTYScript::TContainersPool nodesFinishedRestoring;

            for (auto&& i : SlotsForRestore) {
                NDaemonController::TIntervalsByDC intervalsNew = SlotsForReplace[i.first];
                if (!i.second.Size() && !intervalsNew.Size())
                    continue;
                TString slotsPoolAddress;
                NRTYScript::TSlotTaskContainer currentNode = nodeStartRestoring;
                if (intervalsNew.Size()) {
                    NDaemonController::TAction::TPtr actionAllocate = new NDaemonController::TAllocateSameSlotsAction(CType, i.first, RTYSERVER_SERVICE, intervalsNew, NewSlotsPool);
                    currentNode = currentNode.AddNext(*GetScript(), actionAllocate);

                    slotsPoolAddress = "$" + currentNode.GetName() + "-action.task.result_pool";

                    NDaemonController::TAction::TPtr actionAddSlots = new NDaemonController::TActivateSlotsAction(slotsPoolAddress, CType, i.first, ServiceType);
                    currentNode = currentNode.AddNext(*GetScript(), actionAddSlots);
                }

                TVector<TString> slotPools;
                if (!!slotsPoolAddress)
                    slotPools.push_back(slotsPoolAddress);
                if (i.second.Size())
                    slotPools.push_back(i.second.Serialize().GetStringRobust());

                currentNode = currentNode.AddNext(*GetScript(), new NRTYDeploy::TAggregateAction(slotPools));

                const TString slotsPoolAddressFull = "$" + currentNode.GetName() + "-action.task.result_pool";

                NDaemonController::TAction::TPtr actionRestore = new NRTYDeploy::TRestoreIndexAction(i.first, CType, SlotsNotUsefulForRestoreSource,
                    slotsPoolAddressFull, NDaemonController::apStartAndWait);
                NDaemonController::TAction::TPtr actionActivateSlots = new NDaemonController::TSimpleSearchmapModifAction(slotsPoolAddressFull, CType, i.first, "enable_search,enable_indexing", ServiceType, true, false);

                NRTYScript::TSlotTaskContainer restoreNode = currentNode.AddNext(*GetScript(), actionRestore);

                currentNode = restoreNode.AddNext(*GetScript(), actionActivateSlots);

                if (SlotsForRelease[i.first].size()) {
                    NDaemonController::TAction::TPtr actionRelease = new NDaemonController::TReleaseSlotsAction(SlotsForRelease[i.first], CType, i.first, ServiceType, false, false);
                    currentNode = currentNode.AddNext(*GetScript(), actionRelease);
                }

                NRTYScript::TSlotTaskContainer logicSeparator = currentNode.AddNext<NRTYScript::TFinishedChecker>(*GetScript(), new NRTYDeploy::TLogicAction());

                nodesFinishedRestoring.Add(logicSeparator);
            }

            if (DeployProxies) {
                auto nodeStartDeployProxiesAfterRestore = nodesFinishedRestoring.AddNext(*GetScript(), new NRTYDeploy::TLogicAction());
                {
                    auto currentSPDeploy = nodeStartDeployProxiesAfterRestore.AddNext(*GetScript(), new NRTYDeploy::TDeployAction(SEARCH_PROXY_SERVICE, CType, "NEW",
                        NDaemonController::apStart, SEARCH_PROXY_SERVICE, MayBeDeadIntervalPercentage));
                    currentSPDeploy.AddNext(*GetScript(), new NRTYDeploy::TWaitAsyncAction(currentSPDeploy.GetName()));

                    auto currentIPDeploy = nodeStartDeployProxiesAfterRestore.AddNext(*GetScript(), new NRTYDeploy::TDeployAction(INDEXER_PROXY_SERVICE, CType, "NEW",
                        NDaemonController::apStart, INDEXER_PROXY_SERVICE, MayBeDeadIntervalPercentage));
                    currentIPDeploy.AddNext(*GetScript(), new NRTYDeploy::TWaitAsyncAction(currentIPDeploy.GetName()));
                }
            }

        } catch (...) {
            SetStatus(ctsFailed, TClusterTask::rsFailedOnConstruction, CurrentExceptionMessage());
        }
    }

};
