#include "script_replica.h"
#include "slots_placement.h"
#include "filters.h"

#include <saas/deploy_manager/meta/cluster.h>
#include <saas/deploy_manager/modules/resources/resources.h>
#include <saas/deploy_manager/server/client/client.h>
#include <saas/deploy_manager/scripts/allocate_slots/common/intervals.h>
#include <saas/deploy_manager/scripts/cluster/cluster_task.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/scripts/common/scripts_helper.h>
#include <saas/util/external/diff.h>
#include <library/cpp/json/json_reader.h>
#include <util/string/type.h>

namespace NRTYDeploy {

    bool TScriptAllocateSlotsReplicas::Process(IDeployInfoRequest& request) {
        TClusterTask::TCgiContext context = TClusterTask::TCgiContext::Parse(request.GetRD().CgiParam);
        NDaemonController::TIntervalsByDC intervals;
        NRTYCluster::TSlotsAllocator allocator;
        const TString& intervalsStr = request.GetRD().CgiParam.Get("intervals");
        const ui32 replicasCount = FromString<ui32>(request.GetRD().CgiParam.Get("replicas_count"));
        NJson::TJsonValue intervalsJson;
        if (!ReadJsonFastTree(intervalsStr, &intervalsJson)) {
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Incorrect intervals format " << intervalsStr;
            return false;
        }

        if (!intervals.DeserializeFromJson(intervalsJson)) {
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Incorrect intervals info " << intervalsStr;
            return false;
        }

        const TString& allocatorStr = request.GetRD().CgiParam.Get("allocator");
        NJson::TJsonValue allocatorJson;
        if (!ReadJsonFastTree(allocatorStr, &allocatorJson)) {
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Incorrect allocator format " << intervalsStr;
            return false;
        }

        if (!allocator.DeserializeFromJson(allocatorJson)) {
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Incorrect allocator info " << intervalsStr;
            return false;
        }

        const TString& ctype = context.CType;
        const TString& service = context.Service;
        const TString& serviceType = context.ServiceType;

        NRTYDeployInfo::IDeployComponentInfo::TPtr info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(serviceType);
        info->SetInfo(&request, ctype);
        NRTYDeployInfo::IDeployServiceInfo::TPtr serviceInfo = info->BuildServiceInfo(service);

        NSaas::TClusterLocker clusterPtr = NSaas::TClusterLocker::DefineMutable(request, ctype);
        if (!clusterPtr) {
            request.Output() << "HTTP/1.1 500 \r\n\r\n";
            request.Output() << "Resource " << "/common/" << ctype << "/cluster.meta is not available for modification";
            return false;
        }
        SetData(clusterPtr.GetContent(), "");

        NRTYCluster::TCTypeCluster cluster;

        auto&& clusterInfo = request.GetCommonData().GetResourcesManager().GetCluster(serviceType, service);

        if (!clusterInfo.GetCTypeCluster(ctype, cluster)) {
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Incorrect ctype: " << ctype;
            return false;
        }

        if (intervals.GetInfo().size() != 1 || !intervals.HasDC("*")) {
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Incorrect intervals description for replics construction: " << intervalsStr;
            return false;
        }

        const TVector<NSearchMapParser::TShardsInterval>& intervalShards = intervals.GetInfo().find("*")->second;
        for (ui32 i = 0; i < intervalShards.size(); ++i) {
            for (ui32 j = i + 1; j < intervalShards.size(); ++j) {
                if (intervalShards[i].Intersection(intervalShards[j])) {
                    request.Output() << "HTTP/1.1 400 \r\n\r\n";
                    request.Output() << "Incorrect intervals replicas construction: non-zero-intersection";
                    return false;
                }
            }
        }

        TMap<NSearchMapParser::TShardsInterval, THashSet<TString>> forbiddenIntervalsShortSlot;
        if (!allocator.AllowSameHostReplica) {
            for (const auto& usedSlot: serviceInfo->GetUsedSlots()) {
                forbiddenIntervalsShortSlot[usedSlot.Shards].insert(usedSlot.GetShortHostName());
            }
        }

        NSearchMapParser::TSlotsPool poolResult;
        TFiltersAggregator filters(allocator, &request.GetCommonData().GetControllersChecker(), info, serviceInfo, intervalShards.size());
        NRTYCluster::TCTypeCluster::TPtr filteredCluster = cluster.FilterSlots(filters.GetGlobalFilters());

        for (ui32 replica = 0; replica < replicasCount; ++replica) {
            NRTYCluster::TCTypeCluster::TPtr newSlots = filteredCluster->FilterSlots(filters.GetAllocFilters());
            allocator.RotatePrefDCs();
            if (newSlots->GetSlots().empty()) {
                request.Output() << "HTTP/1.1 400 \r\n\r\n";
                request.Output() << "We haven't enough slots";
                return false;
            }
            if (allocator.AllowSameHostReplica) {
                ui32 index = 0;
                for (auto&& newSlot : newSlots->GetSlots()) {
                    NSearchMapParser::TSearchMapHost smh(newSlot.second.FullHost(), intervalShards[index], NSearchMapParser::Master, newSlot.second.Port, newSlot.second.IndexPort());
                    poolResult.Add(smh);
                    filters.AddInPool(smh);
                    DEBUG_LOG << "Selected slot: " << smh.GetSlotName() << "for interval " << intervalShards[index] << Endl;
                    ++index;
                }
            } else {
                TMaybe<TVector<NRTYCluster::TSlotData>> placement = PlaceSlots(newSlots->GetSlots(), forbiddenIntervalsShortSlot, intervalShards);
                if (placement.Empty()) {
                    request.Output() << "HTTP/1.1 400 \r\n\r\n";
                    request.Output() << "Failed to place slots";
                    return false;
                }
                for (ui32 index = 0; index < intervalShards.size(); ++index) {
                    const NRTYCluster::TSlotData& newSlot = placement.GetRef()[index];
                    forbiddenIntervalsShortSlot[intervalShards[index]].insert(newSlot.ShortHost());
                    NSearchMapParser::TSearchMapHost smh(newSlot.FullHost(), intervalShards[index], NSearchMapParser::Master, newSlot.Port, newSlot.IndexPort());
                    poolResult.Add(smh);
                    filters.AddInPool(smh);
                    DEBUG_LOG << "Selected slot: " << smh.GetSlotName() << "for interval " << intervalShards[index] << Endl;
                }
            }
        }

        serviceInfo->AddNewSlots(poolResult, *clusterPtr);
        TString errorMessage;
        if (!clusterPtr->Validate(errorMessage)) {
            DEBUG_LOG << "Can't store cluster meta: " << errorMessage << Endl;
            request.Output() << "HTTP/1.1 500 \r\n\r\n";
            request.Output() << "Incorrect new cluster: " << errorMessage;
            return false;
        }
        SetData("", clusterPtr.GetContent());
        if (!clusterPtr.Save()) {
            request.Output() << "HTTP/1.1 500 \r\n\r\n";
            request.Output() << "Incorrect storage structure";
            return false;
        }
        request.Output() << "HTTP/1.1 200 \r\n\r\n";
        request.Output() << poolResult.Serialize().GetStringRobust();

        return true;
    };

    IScript::TFactory::TRegistrator<TScriptAllocateSlotsReplicas> TScriptAllocateSlotsReplicas::Registrator("allocate_slots_replicas");
}
