#include "task.h"
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/scripts/set_conf/save_current_config_action.h>
#include <saas/deploy_manager/scripts/logic/action.h>
#include <saas/deploy_manager/scripts/check_configs/action.h>
#include <saas/deploy_manager/scripts/process_alerts/service_actions/action_create.h>
#include <saas/library/daemon_base/controller_actions/delete_file.h>
#include <saas/library/daemon_base/controller_actions/get_status.h>
#include <saas/library/daemon_base/controller_actions/restart.h>
#include <saas/library/daemon_base/controller_actions/take_file.h>
#include <saas/library/daemon_base/controller_actions/download_configs_from_dm.h>
#include <saas/library/daemon_base/controller_actions/execute_script.h>
#include <library/cpp/json/json_reader.h>

namespace NRTYDeploy {

    NDaemonController::TAction::TPtr TDeployTask::BuildScriptExecute(const NSearchMapParser::TSearchMapHost& info, const TNodesStructure& nodes) const {
        if (CommonData->GetConfig().GetUseExecuteScriptInDeploy()) {
            THolder<NDaemonController::TExecuteScriptAction> executeAction = MakeHolder<NDaemonController::TExecuteScriptAction>(NDaemonController::apStartAndWait);
            NRTYScript::TSlotTaskContainer startExecuteScriptNode = executeAction->GetScript().AddAction(info.Name, info.ControllerPort(), "", new NDaemonController::TDeleteFileAction("./"), true);
            TFileContentGenerator::TContext context;
            context.SlotInfo = info.GetSlotInfo("default", Service, ServiceType, CType);
            for (ui32 file = 0; file < nodes.size(); ++file) {
                TString rename = nodes[file]->GetRename(context);
                if (!rename)
                    continue;
                TString url;
                if (!nodes[file]->SerializeWithContext(url, context))
                    continue;
                startExecuteScriptNode.AddNext<NRTYScript::TFinishedChecker>(executeAction->GetScript(), new NDaemonController::TTakeFileAction(
                    "./" + rename,
                    CommonData->GetDeployManagerBalanserHost(),
                    CommonData->GetDeployManagerBalanserPort(),
                    CommonData->GetDeployManagerBalanserUriPrefix() + "get_conf?filename=" + url, Default<TString>(), 1000));
            }
            return executeAction.Release();
        } else {
            NSaas::TSlotInfo si = info.GetSlotInfo("default", Service, ServiceType, CType);
            if (!NSlotNameUtil::IsNormalDC(si.DC)) {
                si.DC = TDatacenterUtil::Instance().GetDatacenter(info.Name);
            }
            THolder<NDaemonController::TDownloadConfigsFromDmAction> downloadAction = MakeHolder<NDaemonController::TDownloadConfigsFromDmAction>(NDaemonController::apStartAndWait, Version, si);
            downloadAction->MutableDMOptions().Host = CommonData->GetDeployManagerBalanserHost();
            downloadAction->MutableDMOptions().Port = CommonData->GetDeployManagerBalanserPort();
            downloadAction->MutableDMOptions().UriPrefix = CommonData->GetDeployManagerBalanserUriPrefix();
            return downloadAction.Release();
        }
    }

    NRTYScript::TSlotTaskContainer TDeployTask::BuildChainForDeploy(NRTYScript::TRTYClusterScript::TPtr& script, const TNodesStructure& nodes, const TVector<NSearchMapParser::TSearchMapHost>& hosts, TDuration stopTimeout, const NRTYScript::TSlotTaskContainer& startNode) {
        TVector<NRTYScript::TTaskContainer> finishContainers;
        for (auto& host : hosts) {
            ui32 hostControllerPort = host.ControllerPort();
            const TString& hostName = host.Name;

            NDaemonController::TStatusAction* statusAction = new NDaemonController::TStatusAction();

            NRTYScript::TSlotTaskContainer startChecker = script->AddDependentAction<NRTYScript::TSucceededChecker>(hostName, hostControllerPort, "", statusAction, startNode.GetContainer(), true);
            NRTYScript::TSlotTaskContainer current = startChecker.AddNext<NRTYScript::TSucceededChecker>(*script, BuildScriptExecute(host, nodes));
            current = current.AddNext<NRTYScript::TSucceededChecker>(*script, new NDaemonController::TRestartAction(NDaemonController::apStartAndWait, false, false, stopTimeout));
            current = current.AddNext<NRTYScript::TSucceededChecker>(*script, (new NDaemonController::TStatusAction())->AddVariantServerStatus("OK"));
            current = current.AddNext<NRTYScript::TSucceededChecker>(*script, new NRTYDeploy::TLogicAction());
            script->AddSeqInfo(startChecker.GetContainer(), current.GetContainer(), new NRTYScript::TFailedChecker());
            finishContainers.push_back(current.GetContainer());
        }
        return script->AddDependentAction(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(), CommonData->GetDeployManagerBalanserUriPrefix(), new NRTYDeploy::TLogicAction(), finishContainers);
    }

    void TDeployTask::BuildDeployScriptReplica(const TVector<NSearchMapParser::TSearchMapHost>& hosts, const TNodesStructure& nodes, NRTYScript::TRTYClusterScript::TPtr& script, TDuration stopTimeout, NRTYScript::TSlotTaskContainer& setConfContainer) {
        TInfoByInterval infoByInterval;

        for (auto& i : hosts) {
            if (!SlotsFilter || SlotsFilter->SlotExists(i.GetSlotName())) {
                infoByInterval[i.Shards].Count++;
                infoByInterval[i.Shards].Hosts.push_back(i);
            }
        }

        TMap<NSearchMapParser::TShardsInterval, TVector<NSearchMapParser::TSearchMapHost>> hostSegments;
        NRTYScript::TContainersPool tasksConfigServices;
        for (auto&& i : infoByInterval) {
            TVector<NSearchMapParser::TSearchMapHost> hosts;
            NRTYScript::TSlotTaskContainer lastTaskCont = setConfContainer;
            for (ui32 host = 0; host < i.second.Hosts.size(); ++host) {
                hosts.push_back(i.second.Hosts[host]);
                if (hosts.size() >= MayBeDeadInIntervalPercentage * i.second.Count || (host == i.second.Hosts.size() - 1)) {
                    lastTaskCont = BuildChainForDeploy(script, nodes, hosts, stopTimeout, lastTaskCont);
                    hosts.clear();
                }
            }
            tasksConfigServices.Add(lastTaskCont);
        }
        if (infoByInterval.size())
            tasksConfigServices.AddNext(*script, new NRTYDeploy::TLogicAction());
        else {
            script->AddAction(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(), CommonData->GetDeployManagerBalanserUriPrefix(), new NRTYDeploy::TLogicAction());
        }
    }

    void TDeployTask::BuildAlertsTasks() {
        if (!CommonData->GetConfig().GetAlertsConfig().GetEnabled()) {
            return;
        }
        const TSet<TString> alertComps({RTYSERVER_SERVICE, META_SERVICE_SERVICE});
        if (!alertComps.contains(ServiceType)) {
            return;
        }
        if (Service == UNUSED_SERVICE) {
            return;
        }
        if (!CType.StartsWith(CommonData->GetConfig().GetAlertsConfig().GetCtypesPrefix())) {
            return;
        }
        NRTYScript::TSlotTaskContainer taskAlerts = GetScript()->AddAction(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(), CommonData->GetDeployManagerBalanserUriPrefix(),
            new TAlertsCreateAction(Service, CType));
        GetScript()->AddDependentAction<NRTYScript::TSucceededChecker>(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(),
            CommonData->GetDeployManagerBalanserUriPrefix(), new NRTYDeploy::TLogicAction(), taskAlerts);
    }

    TDeployTask::TDeployTask(const TClusterTask::TCgiContext& context, TString version, NRTYDeploy::ICommonData* commonData,
        float mayBeDeadInIntervalPercentage,
        TAtomicSharedPtr<NSearchMapParser::TSlotsPool> slotsFilter, const TString& forceServices, bool diffSlotsOnly)
        : TClusterTask(context, commonData, DEPLOY_TASK_TYPE)
        , Version(!!version ? version : "NEW")
        , MayBeDeadInIntervalPercentage(mayBeDeadInIntervalPercentage)
        , SlotsFilter(slotsFilter)
        , DiffSlotsOnly(diffSlotsOnly)
    {
        TVector<TString> rf = SplitString(forceServices, ",");
        ForceServices.insert(rf.begin(), rf.end());
    }

    void TDeployTask::DoBuildTask() {
        try {

            TStringStream errors;

            NRTYDeployInfo::IDeployComponentInfo::TPtr component(NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(ServiceType));

            if (!component) {
                SetStatus(ctsFailed, TClusterTask::rsFailedOnConstruction, "Incorrect service type: " + ServiceType);
                return;
            }

            component->SetInfo(CommonData, CType);

            NRTYDeployInfo::IDeployServiceInfo::TPtr builder = component->BuildServiceInfo(Service);

            TFileContentGenerator::TContext context;
            context.SlotInfo.CType = CType;
            context.SlotInfo.Service = Service;
            context.SlotInfo.ServiceType = ServiceType;
            context.ForceServices = ForceServices;

            if (!builder->BuildFilesInfo(context, errors, Version)) {
                SetStatus(ctsFailed, TClusterTask::rsFailedOnConstruction, "Can't build deploy data: " + errors.Str());
                return;
            }

            TVector<NRTYScript::TTaskContainer> predTasks;
            for (auto&& i : context.ForceStructures) {
                predTasks.push_back(GetScript()->AddIndependentAction(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(), CommonData->GetDeployManagerBalanserUriPrefix(),
                    new TSaveCurrentConfigAction(i.first, i.second)));
            }

            NRTYScript::TSlotTaskContainer taskSaveConfMain = GetScript()->AddDependentAction<NRTYScript::TSucceededChecker>(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(), CommonData->GetDeployManagerBalanserUriPrefix(),
                new TSaveCurrentConfigAction(builder->GetCurrentConfigsPath(), builder->GetNodes()), predTasks);
            Version = "$" + taskSaveConfMain.GetName() + "-action.task.version";
            if (DiffSlotsOnly) {
                if (!SlotsFilter) {
                    SlotsFilter.Reset(new NSearchMapParser::TSlotsPool);
                }
                NDaemonController::TCheckConfigsAction checkConfigAction(CType, Service, ServiceType, true, "", "");
                NDaemonController::TControllerAgent agent(CommonData->GetDeployManagerBalanserHost(), CommonData->GetDeployManagerBalanserPort(), nullptr, CommonData->GetDeployManagerBalanserUriPrefix());
                agent.ExecuteAction(checkConfigAction);
                NJson::TJsonValue confSlots = checkConfigAction.GetResult()[Service];
                for (NJson::TJsonValue::TMapType::const_iterator slot = confSlots.GetMap().begin(); slot != confSlots.GetMap().end(); ++slot) {
                    bool shouldAdd = false;
                    for (NJson::TJsonValue::TMapType::const_iterator file = slot->second.GetMap().begin(); file != slot->second.GetMap().end(); ++file) {
                        if (file->second["from_host"].GetString() != file->second["last_deployed"].GetString()
                            || file->second["from_host"].GetString() != file->second["last_stored"].GetString()) {
                            shouldAdd = true;
                            break;
                        }
                    }
                    if (shouldAdd) {
                        TVector<TString> hostPort = SplitString(slot->first, ":");
                        if (hostPort.size() != 2) {
                            continue;
                        }
                        NSearchMapParser::TSearchMapHost smh(hostPort[0], TInterval<NSearchMapParser::TShardIndex>(0, 0), NSearchMapParser::Master,
                            FromString<ui16>(hostPort[1]), FromString<ui16>(hostPort[1]) + 2);
                        SlotsFilter->Add(smh);
                    }
                }
            }
            BuildDeployScriptReplica(builder->GetUsedSlots(), builder->GetNodes(), GetScript(), builder->GetStopTimeout(), taskSaveConfMain);
            BuildAlertsTasks();
        } catch (...) {
            SetStatus(ctsFailed, TClusterTask::rsFailedOnConstruction, CurrentExceptionMessage());
        }
    }

};
