#include "script.h"
#include <saas/deploy_manager/meta/cluster.h>
#include <saas/deploy_manager/modules/resources/resources.h>
#include <saas/deploy_manager/scripts/broadcast/broadcast_action.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/server/client/client.h>
#include <library/cpp/http/misc/httpcodes.h>

#include <util/string/split.h>
#include <util/string/type.h>
#include <util/generic/vector.h>


namespace NRTYDeploy {

    struct TParsedFields {
        TVector<NRTYCluster::TSlotData> Slots;
        TVector<NRTYCluster::TSlotData> Exclude;
        TVector<NRTYCluster::TSlotData> EndpointSets;
        TVector<TTagsInfo::TTag> Tags;
        TMaybe<bool> UseContainers;
    };

    bool ParseFormFields(IDeployInfoRequest& request, TParsedFields& parsedFields) {
        const TCgiParameters& cgi = request.GetRD().CgiParam;

        for (const auto& slotV: StringSplitter(cgi.Get("slots")).Split(',').SkipEmpty()) {
            parsedFields.Slots.emplace_back();
            if (!NRTYCluster::TSlotData::Parse(slotV.Token(), parsedFields.Slots.back())) {
                request.Output() << "HTTP/1.1 " << HttpCodeStrEx(400) << "\r\n\r\n";
                request.Output() << "invalid slot " << slotV.Token();
                return false;
            }
        }

        for (const auto& excludeV: StringSplitter(cgi.Get("exclude")).Split(',').SkipEmpty()) {
            parsedFields.Exclude.emplace_back();
            if (!NRTYCluster::TSlotData::Parse(excludeV.Token(), parsedFields.Exclude.back())) {
                request.Output() << "HTTP/1.1 " << HttpCodeStrEx(400) << "\r\n\r\n";
                request.Output() << "invalid slot " << excludeV.Token();
                return false;
            }
        }

        for (const auto& eps: StringSplitter(cgi.Get("endpointsets")).Split(',').SkipEmpty()) {
            parsedFields.EndpointSets.emplace_back();
            if (!NRTYCluster::TSlotData::ParseAsEndpointSet(eps.Token(), parsedFields.EndpointSets.back())) {
                request.Output() << "HTTP/1.1 " << HttpCodeStrEx(400) << "\r\n\r\n";
                request.Output() << "invalid endpointset " << eps.Token();
                return false;
            }
        }

        for (const auto& nannyService: StringSplitter(cgi.Get("nanny_services")).Split(',').SkipEmpty()) {
            parsedFields.Tags.emplace_back(nannyService.Token(), TTagsInfo::TTag::NANNY_SERVICE);
        }
        if (cgi.Has("use_container_names")) {
            parsedFields.UseContainers = IsTrue(cgi.Get("use_container_names"));
        }
        return true;
    }

    bool TScriptModifyTagsInfo::Process(IDeployInfoRequest& request) {
        const TCgiParameters& cgi = request.GetRD().CgiParam;
        TString service = cgi.Get("service");
        if (!service)
            service = "*";
        const TString& serviceType = cgi.Get("service_type");
        if (!NRTYDeployInfo::IDeployComponentInfo::TFactory::Has(serviceType)) {
            request.Output() << "HTTP/1.1 " << HttpCodeStrEx(400) << "\r\n\r\n";
            request.Output() << "invalid service_type " << serviceType;
            return false;
        }
        const TString& ctype = cgi.Get("ctype");
        const TString& action = cgi.Get("action");

        NJson::TJsonValue result;
        if (action == "get") {
            TTagsInfo::TServiceResourcesInfo::TPtr sri = request.GetResourcesManager().GetServiceResourcesInfo(ctype, serviceType, service);
            if (!sri) {
                request.Output() << "HTTP/1.1 " << HttpCodeStrEx(404) << "\r\n\r\n";
                return false;
            }
            sri->Serialize(result);
        } else if (action == "add") {
            TTagsInfo::TServiceResourcesInfo::TPtr sri = request.GetResourcesManager().GetServiceResourcesInfo(ctype, serviceType, service);
            if (!sri) {
                request.Output() << "HTTP/1.1 " << HttpCodeStrEx(404) << "\r\n\r\n";
                return false;
            }
            TParsedFields parsedFields;
            if (!ParseFormFields(request, parsedFields)) {
                return false;
            }
            auto tags = sri->GetTags();
            auto slots = sri->GetSlotList();
            auto excludedSlots = sri->GetExcludeSlots();
            auto endpointSets = sri->GetEndpointSets();
            bool useContainers = sri->GetUseContainers();

            slots.insert(slots.end(), parsedFields.Slots.begin(), parsedFields.Slots.end());
            excludedSlots.insert(excludedSlots.end(), parsedFields.Exclude.begin(), parsedFields.Exclude.end());
            endpointSets.insert(endpointSets.end(), parsedFields.EndpointSets.begin(), parsedFields.EndpointSets.end());
            tags.insert(tags.end(), parsedFields.Tags.begin(), parsedFields.Tags.end());
            if (parsedFields.UseContainers.Defined()) {
                useContainers = parsedFields.UseContainers.GetRef();
            }

            TTagsInfo::TServiceResourcesInfo newInfo(std::move(tags), useContainers,
                std::move(slots), std::move(excludedSlots), std::move(endpointSets));
            request.GetResourcesManager().SaveServiceResourcesInfo(ctype, serviceType, service, newInfo);
            InvalidateCaches(request, ctype, serviceType, service);
            newInfo.Serialize(result);
        } else if (action == "rm") {
            TTagsInfo::TServiceResourcesInfo::TPtr sri = request.GetResourcesManager().GetServiceResourcesInfo(ctype, serviceType, service);
            if (!sri) {
                request.Output() << "HTTP/1.1 " << HttpCodeStrEx(404) << "\r\n\r\n";
                return false;
            }
            TParsedFields parsedFields;
            if (!ParseFormFields(request, parsedFields)) {
                return false;
            }

            SortUnique(parsedFields.Slots);
            SortUnique(parsedFields.Exclude);
            SortUnique(parsedFields.Tags);

            TParsedFields newFields;
            SetIntersection(sri->GetSlotList().cbegin(), sri->GetSlotList().cend(),
                            parsedFields.Slots.cbegin(), parsedFields.Slots.cend(),
                            std::back_inserter(newFields.Slots));
            SetIntersection(sri->GetExcludeSlots().cbegin(), sri->GetExcludeSlots().cend(),
                            parsedFields.Exclude.cbegin(), parsedFields.Exclude.cend(),
                            std::back_inserter(newFields.Exclude));
            SetIntersection(sri->GetEndpointSets().cbegin(), sri->GetEndpointSets().cend(),
                            parsedFields.EndpointSets.cbegin(), parsedFields.EndpointSets.cend(),
                            std::back_inserter(newFields.EndpointSets));
            SetIntersection(sri->GetTags().cbegin(), sri->GetTags().cend(),
                            parsedFields.Tags.cbegin(), parsedFields.Tags.cend(),
                            std::back_inserter(newFields.Tags));

            TTagsInfo::TServiceResourcesInfo newInfo(std::move(newFields.Tags), sri->GetUseContainers(),
                std::move(newFields.Slots), std::move(newFields.Exclude), std::move(newFields.EndpointSets));
            request.GetResourcesManager().SaveServiceResourcesInfo(ctype, serviceType, service, newInfo);
            InvalidateCaches(request, ctype, serviceType, service);
            newInfo.Serialize(result);
        } else if (action == "set") {
            TParsedFields parsedFields;
            if (!ParseFormFields(request, parsedFields)) {
                return false;
            }

            bool useContainers = parsedFields.UseContainers.Defined() ? parsedFields.UseContainers.GetRef() : USE_CONTAINERS_DEFAULT_VALUE;
            TTagsInfo::TServiceResourcesInfo newInfo(std::move(parsedFields.Tags), useContainers,
                std::move(parsedFields.Slots), std::move(parsedFields.Exclude), std::move(parsedFields.EndpointSets));
            request.GetResourcesManager().SaveServiceResourcesInfo(ctype, serviceType, service, newInfo);
            InvalidateCaches(request, ctype, serviceType, service);
            newInfo.Serialize(result);
        } else if (action == "invalidate") {
            result = InvalidateCaches(request, ctype, serviceType, service, true);
        } else {
            request.Output() << "HTTP/1.1 " << HttpCodeStrEx(400) << "\r\n\r\n";
            request.Output() << "invalid action " << action;
            return false;
        }
        request.Output() << "HTTP/1.1 " << HttpCodeStrEx(200) << "\r\n\r\n";
        request.Output() << NJson::WriteJson(result, true, true);
        return true;
    };


    NJson::TJsonValue TScriptModifyTagsInfo::InvalidateCaches(IDeployInfoRequest& request, const TString& ctype, const TString& serviceType, const TString& serviceName, bool reloadAfter) const {
        const TString command = "invalidate_tags_info&cache_ctype=" + ctype + "&cache_service_type=" + serviceType + "&cache_service=" + serviceName + (reloadAfter ? "&reload_after=true" : "");
        NDaemonController::TControllerAgent agent(request.GetDeployManagerBalanserHost(), request.GetDeployManagerBalanserPort(), nullptr, request.GetDeployManagerBalanserUriPrefix());
        NJson::TJsonValue result;
        for (const auto& dmCtype : request.GetResourcesManager().GetCTypes()) {
            NDaemonController::TBroadcastAction broadcast(command, dmCtype, DEPLOY_MANAGER_SERVICE, DEPLOY_MANAGER_SERVICE);
            agent.ExecuteAction(broadcast);
            result.InsertValue(dmCtype, broadcast.GetResult());
        }
        return result;
    }

    IScript::TFactory::TRegistrator<TScriptModifyTagsInfo> TScriptModifyTagsInfo::Registrator("modify_tags_info");
}
