#include "get_files.h"

#include <saas/deploy_manager/meta/cluster.h>
#include <saas/deploy_manager/modules/nanny/nanny.h>
#include <saas/deploy_manager/modules/resources/resources.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <saas/deploy_manager/server/client/client.h>
#include <library/cpp/deprecated/split/split_iterator.h>

namespace NRTYDeploy {

    void TScriptGetFiles::GetTagsInfoByServiceName(
        const TString& ctype,
        const TString& serviceType,
        const TString& serviceName,
        NJson::TJsonValue& tagsInfo,
        TFiles& filesVersionsCounts,
        IDeployInfoRequest& request
    ) {

        bool customTags = true;
        TTagsInfo::TServiceResourcesInfo::TPtr sriPtr = request.GetResourcesManager().GetServiceResourcesInfo(ctype, serviceType, serviceName);
        if (!sriPtr) {
            sriPtr = request.GetResourcesManager().GetServiceResourcesInfo(ctype, serviceType, "*");
            customTags = false;
            if (!sriPtr) {
                ythrow yexception() << "failed to get tags info";
            }
        }

        for (auto&& itTags: sriPtr->GetTags()) {
            const TString tagName = itTags.Name.substr(0, itTags.Name.find('@'));
            const NJson::TJsonValue* cachedData = nullptr; // unordered map never moves values
            {
                TReadGuard rg(TagsCacheLock);
                if (TagsCache.contains(tagName)) {
                    cachedData = &TagsCache[tagName];
                }
            }
            if (cachedData == nullptr) {
                TWriteGuard wg(TagsCacheLock);
                if (!TagsCache.contains(tagName)) {
                    NJson::TJsonValue& entry = TagsCache[tagName];
                    if(!request.GetNannyModule().GetRuntimeAttrs(tagName, entry)) {
                        entry = NJson::JSON_MAP;
                    }
                    cachedData = &entry;
                } else {
                    cachedData = &TagsCache[tagName];
                }
            }
            const auto& cachedMap = cachedData->GetMap();
            const auto filesIt = cachedMap.find("files");
            if (filesIt != cachedMap.end())
                tagsInfo.InsertValue("files", filesIt->second);
            else
                tagsInfo.InsertValue("files", NJson::JSON_MAP);

            const auto groupsIt = cachedMap.find("groups");
            if (groupsIt != cachedMap.end())
                tagsInfo.InsertValue("groups", groupsIt->second);
            else
                tagsInfo.InsertValue("groups", NJson::JSON_MAP);

            tagsInfo["tags"].AppendValue(tagName);
            tagsInfo["custom_tags"] = customTags;
            if (filesIt != cachedMap.end()) {
                for (const auto& file : filesIt->second.GetMap()) {
                    const TString version = NJson::WriteJson(file.second, false, true);
                    ++filesVersionsCounts[file.first][version].Count;
                }
            }
        }
    }

    void TScriptGetFiles::SetColors(NJson::TJsonValue& services, TFiles& filesVersionsCounts) {
        struct TCountGreater {
            inline bool operator() (const std::pair<TString, TFileVersionInfo>& a, const std::pair<TString, TFileVersionInfo>& b) const {
                return a.second.Count > b.second.Count;
            }
        };

        for (auto& file : filesVersionsCounts) {
            TVector<std::pair<TString, TFileVersionInfo>> versions(file.second.begin(), file.second.end());
            Sort(versions.begin(), versions.end(), TCountGreater());
            ui32 color = 0;
            for (const auto& version : versions) {
                file.second[version.first].Color = color++;
            }
        }
        for (const auto& service : services.GetMap()) {
            if (!service.second.Has("files"))
                continue;
            for (const auto& file : service.second["files"].GetMap()) {
                TString version = NJson::WriteJson(file.second, false, true);
                services[service.first]["files"][file.first]["color"] = filesVersionsCounts[file.first][version].Color;
            }
        }
    }

    bool TScriptGetFiles::Process(IDeployInfoRequest& request) {
        NJson::TJsonValue result(NJson::JSON_MAP);
        NJson::TJsonValue services(NJson::JSON_MAP);

        const TString& serviceName = request.GetRD().CgiParam.Get("service");
        const TString& ctype = request.GetRD().CgiParam.Get("ctype");
        const TString& serviceType = request.GetRD().CgiParam.Get("service_type");

        if (!ctype || !serviceType) {
            request.Output() << "HTTP/1.1 400 \r\n\r\n";
            request.Output() << "Ctype or serviceType is empty" << Endl;
            return false;
        }

        TFiles filesVersionsCounts;
        if (!serviceName) {
            NRTYDeployInfo::IDeployComponentInfo::TPtr info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(serviceType);
            info->SetInfo(&request, ctype);
            const NSearchMapParser::TSearchMap* ism;
            try {
                ism = &info->SearchMap();
            } catch (yexception& e) {
                request.Output() << "HTTP/1.1 500 \r\n\r\n";
                request.Output() <<  "searchmap failed: " << e.what();
                return false;
            }

            try {
                for (auto& service : ism->GetInternalSearchMap()) {
                    NJson::TJsonValue tagsInfo(NJson::JSON_MAP);
                    GetTagsInfoByServiceName(ctype, serviceType, service.Name, tagsInfo, filesVersionsCounts, request);

                    services.InsertValue(service.Name, tagsInfo);
                }

            } catch (yexception& e) {
                request.Output() << "HTTP/1.1 500 \r\n\r\n";
                request.Output() << "searchmap parsing failed: " << e.what();
                return false;
            }
        } else {
            NJson::TJsonValue tagsInfo(NJson::JSON_MAP);
            GetTagsInfoByServiceName(ctype, serviceType, serviceName, tagsInfo, filesVersionsCounts, request);

            services.InsertValue(serviceName, tagsInfo);
        }

        SetColors(services, filesVersionsCounts);

        result.InsertValue("services", services);
        NJson::TJsonValue& files = result.InsertValue("files", NJson::JSON_ARRAY);
        for (const auto& file : filesVersionsCounts)
            files.AppendValue(file.first);

        const TString replyStr = NJson::WriteJson(result, true, true);

        request.Output() << "HTTP/1.1 200 \r\n\r\n";
        request.Output() << replyStr << Endl;
        return true;
    }

    IScript::TFactory::TRegistrator<TScriptGetFiles> TScriptGetFiles::Registrator("get_files");

}
