#include "projects_script.h"
#include "projects_utils.h"
#include "users_utils.h"

#include <saas/deploy_manager/modules/resources/resources.h>
#include <saas/deploy_manager/scripts/common/deploy/deploy_builder.h>
#include <library/cpp/json/json_reader.h>

namespace NRTYDeploy {

    void SerializeProject(const NDMInterface::TProject& proj, NJson::TJsonValue& result) {
        Proto2Json(proj, result);
        result.InsertValue("id", proj.GetName());
    }

    void AdjustNewProject(IDeployInfoRequest& request, NDMInterface::TProject& proj, const TString& login) {
        const TString& name = proj.GetName();
        if (proj.ComponentsSize() == 0) {
            auto meta = proj.AddComponents();
            meta->SetServiceName(name);
            meta->SetServiceType(META_SERVICE_SERVICE);
            auto search = proj.AddComponents();
            search->SetServiceName(name + "-search");
            search->SetServiceType(RTYSERVER_SERVICE);
            if (proj.GetSuggestNeeded()) {
                auto suggest = proj.AddComponents();
                suggest->SetServiceName(name + "-suggest");
                suggest->SetServiceType(RTYSUGGEST_SERVICE);
            }
        }
        NDMInterface::TUser user = GetUser(request.GetStorage(), login);
        if (user.GetDefaultRole() < NDMInterface::TUser::ADMINISTRATOR)
            ythrow TCodedException(403) << "user " << login << "not permitted to create project";
        NDMInterface::TUser::TRoleInProject& roleInPrj = *user.AddRoleInProject();
        roleInPrj.SetRole(NDMInterface::TUser::ADMINISTRATOR);
        roleInPrj.SetProject(name);
        SaveUser(request.GetStorage(), user);
    }

    void SetProject(IDeployInfoRequest& request, bool newProject, NJson::TJsonValue& report, const TString& login) {
        auto lock = request.GetStorage().WriteLockNode("/projects");
        TMemoryInput in(request.GetBlob().AsCharPtr(), request.GetBlob().Size());
        NJson::TJsonValue json;
        if (!NJson::ReadJsonTree(&in, &json, true))
            ythrow TCodedException(400) << "invalid json";
        NDMInterface::TProject proj;
        Json2Proto(json, proj);
        proj.clear_users();
        if (!newProject && request.GetRD().CgiParam.Has("name"))
            proj.SetName(request.GetRD().CgiParam.Get("name"));
        const TString& name = proj.GetName();
        bool exists = request.GetStorage().ExistsNode("/projects/" + name);
        if (newProject) {
            if (exists)
                ythrow TCodedException(400) << "project " << name << " already exists";
            AdjustNewProject(request, proj, login);
        } else {
            if(!exists)
                ythrow TCodedException(400) << "project " << name << " not exists";
            if (GetUserRoleInProject(request.GetStorage(), login, name) < NDMInterface::TUser::ADMINISTRATOR)
                ythrow TCodedException(403) << "user " << login << "not permitted to change project " << name;
            NDMInterface::TProject old = GetProject(request.GetStorage(), name);
            proj.clear_components();
            for (ui32 i = 0; i < old.ComponentsSize(); ++i)
                *proj.AddComponents() = old.GetComponents(i);
        }

        request.GetStorage().SetValue("/projects/" + name, proj);
        if (newProject)
            SaveConfigVersion(name, request.GetStorage(), nullptr);
        SerializeProject(proj, report);
    }

    void GetServices(IDeployInfoRequest& request, THashSet<TString>& result, const TString& ctype, const TString& serviceType) {
        try {
            NRTYDeployInfo::IDeployComponentInfo::TPtr info = NRTYDeployInfo::IDeployComponentInfo::TFactory::Construct(serviceType);
            info->SetInfo(&request, ctype);
            NSearchMapParser::TSearchMap ism;
            ism = info->SearchMap("*");
            for (auto& i : ism.GetInternalSearchMap())
                result.insert(i.Name);
        } catch (...) {
        }
    }

    void TScriptProjects::RecreateProjects(IDeployInfoRequest& request) {
//        auto lock = request.GetStorage().WriteLockNode("/projects");
        THashSet<TString> metaServices, searchServices;
        const TVector<TString>& ctypes = request.GetResourcesManager().GetCTypes();
        for (auto ctype: ctypes) {
            GetServices(request, metaServices, ctype, META_SERVICE_SERVICE);
            GetServices(request, searchServices, ctype, RTYSERVER_SERVICE);
        }
        TVector<TString> list;
        request.GetStorage().GetNodes("/projects", list);
        for (const auto& name : list)
            if (!request.GetStorage().RemoveNode("/projects/" + name))
                ythrow TCodedException(500) << "cannot remove " << name;
        for (const auto& meta : metaServices) {
            NDMInterface::TProject proj;
            proj.SetName(meta);
            auto comp = proj.AddComponents();
            comp->SetServiceName(meta);
            comp->SetServiceType(META_SERVICE_SERVICE);
            TString search = meta + "-search";
            if (searchServices.contains(search)) {
                comp = proj.AddComponents();
                comp->SetServiceName(search);
                comp->SetServiceType(RTYSERVER_SERVICE);
                searchServices.erase(search);
            }
            TString suggest = meta + "-suggest";
            if (searchServices.contains(suggest)) {
                comp = proj.AddComponents();
                comp->SetServiceName(suggest);
                comp->SetServiceType(RTYSERVER_SERVICE);
                searchServices.erase(suggest);
            }
            AdjustNewProject(request, proj, User.Login);
            request.GetStorage().SetValue("/projects/" + proj.GetName(), proj);
            SaveConfigVersion(meta, request.GetStorage(), nullptr);
        }
        for (const auto& search : searchServices) {
            NDMInterface::TProject proj;
            proj.SetName(search);
            auto comp = proj.AddComponents();
            comp->SetServiceName(search);
            comp->SetServiceType(RTYSERVER_SERVICE);
            TString suggest = search + "-suggest";
            if (searchServices.contains(suggest)) {
                comp = proj.AddComponents();
                comp->SetServiceName(suggest);
                comp->SetServiceType(RTYSERVER_SERVICE);
                searchServices.erase(suggest);
            }
            AdjustNewProject(request, proj, User.Login);
            request.GetStorage().SetValue("/projects/" + proj.GetName(), proj);
            SaveConfigVersion(search, request.GetStorage(), nullptr);
        }
    }

    void TScriptProjects::DoProcess(IDeployInfoRequest& request) {
        const TString& action = request.GetRD().CgiParam.Get("action");
        if (action == "list") {
            NDMInterface::TUser currentUser = GetUser(request.GetStorage(), User.Login);
            THashMap<TString, NDMInterface::TUser::TRole> userRoleInProjects;
            for (const auto& role : currentUser.GetRoleInProject())
                userRoleInProjects[role.GetProject()] = role.GetRole();
            TVector<TString> list;
            auto lock = request.GetStorage().ReadLockNode("/projects");
            request.GetStorage().GetNodes("/projects", list);
            Report.SetType(NJson::JSON_ARRAY);
            for (const auto& proj : list) {
                const auto i = userRoleInProjects.find(proj);
                if (i.IsEnd()) {
                    if (currentUser.GetDefaultRole() < NDMInterface::TUser::WATCHER)
                        continue;
                } else if (i->second < NDMInterface::TUser::WATCHER)
                    continue;
                Report.AppendValue(NJson::JSON_MAP).InsertValue("id", proj);
            }
        } else if (action == "set") {
            SetProject(request, false, Report, User.Login);
        } else if (action == "add") {
            SetProject(request, true, Report, User.Login);
        } else if (action == "get") {
            const TString& name = request.GetRD().CgiParam.Get("name");
            if (!name)
                ythrow TCodedException(400) << "project name not set";
            if (GetUserRoleInProject(request.GetStorage(), User.Login, name) < NDMInterface::TUser::WATCHER)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to watch project " << name;
            auto lock = request.GetStorage().ReadLockNode("/projects");
            NDMInterface::TProject proj = GetProject(request.GetStorage(), name);
            FillProjectUsers(request.GetStorage(), proj);
            SerializeProject(proj, Report);
        } else if (action == "remove") {
            const TString& name = request.GetRD().CgiParam.Get("name");
            if (!name)
                ythrow TCodedException(400) << "project name not set";
            if (GetUserRoleInProject(request.GetStorage(), User.Login, name) < NDMInterface::TUser::ADMINISTRATOR)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to remove project " << name;
            auto lock = request.GetStorage().WriteLockNode("/projects");
            if (!request.GetStorage().RemoveNode("/projects/" + name))
                ythrow TCodedException(500) << "cannot remove";
        } else if (action == "recreate") {
            if (GetUserRoleInProject(request.GetStorage(), User.Login, "") < NDMInterface::TUser::SUPER_USER)
                ythrow TCodedException(403) << "user " << User.Login << " not permitted to recreate projects";
            RecreateProjects(request);
        } else
            ythrow TCodedException(400) << "unknown action " << action;
    }

    TScriptProjects::TFactory::TRegistrator<TScriptProjects> TScriptProjects::Registrator("projects");

}
