#include "idm.h"

#include <maps/infra/yacare/include/yacare.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/json/include/builder.h>
#include <maps/libs/common/include/exception.h>
#include <library/cpp/resource/resource.h>

namespace maps::wiki::aclsrv::idm {
namespace {
const maps::auth::TvmId IDM_TVM_ID = 2001600;

const std::string LOGIN_PARAM = "login";
const std::string ROLE_PARAM = "role";
const std::string SERVICE_SLUG = "wikimaps";
const std::string ROLE_SLUG = "role";
const std::string IDM_INFO_RESOURCE = "IDM_INFO_RESOURCE";

class ParseException : public maps::Exception
{
};

acl::IDMRole parseRoleJson(const std::string& jsonString)
{
    try {
        const auto value = json::Value::fromString(jsonString);
        if (!value.hasField(SERVICE_SLUG) || !value.hasField(ROLE_SLUG)) {
            throw ParseException() << "Unreconzied role format";
        }
        return acl::IDMRole {
            .service = value[SERVICE_SLUG].as<std::string>(),
            .role = value[ROLE_SLUG].as<std::string>()
        };
    } catch (const ParseException& ex) {
        throw;
    } catch (const std::exception& ex) {
        throw ParseException() << "Failed to parse role: " << ex.what();
    }
}

RequestData parseIDMRequest(const std::string& body)
{
    yacare::QueryParams paramsMap;
    yacare::parseUrlencodedBody(body, &paramsMap);

    const auto loginIt = paramsMap.find(LOGIN_PARAM);
    if (loginIt == paramsMap.end() || loginIt->second.size() != 1) {
        throw ParseException() << "Bad parameter: " << LOGIN_PARAM;
    }

    const auto roleIt = paramsMap.find(ROLE_PARAM);
    if (roleIt == paramsMap.end() || roleIt->second.size() != 1) {
        throw ParseException() << "Bad parameter: " << ROLE_PARAM;
    }
    return RequestData {
       loginIt->second.front(),
       parseRoleJson(roleIt->second.front())
    };
}


std::string successResult()
{
    return R"({"code":0})";
}

std::string errorResult(const std::string description)
{
    return R"({"code":1,"error": ")" + description + R"("})";
}

std::string fatalResult(const std::string description)
{
    return R"({"code":1,"fatal": ")" + description + R"("})";
}


} // namespace

bool isIDM(const maps::auth::TvmId tvmId)
{
    return tvmId == IDM_TVM_ID;
}

std::string info()
{
    return NResource::Find(IDM_INFO_RESOURCE);
}

std::string addRole(const std::string& requestBody, acl::ACLGateway gw)
{
    try {
        const auto request = parseIDMRequest(requestBody);
        gw.addIDMRole(request.login, request.role);
    } catch (const ParseException& ex) {
        ERROR() << "Can't parse request: " << ex << " [" << requestBody << "] ";
        return fatalResult("Can't parse request");
    } catch (const std::exception& ex) {
        ERROR() << ex.what() <<  " [" << requestBody << "] ";
        return errorResult("Error adding role");
    }
    return successResult();
}

std::string removeRole(const std::string& requestBody, acl::ACLGateway gw)
{
    try {
        const auto request = parseIDMRequest(requestBody);
        gw.removeIDMRole(request.login, request.role);
    } catch (const ParseException& ex) {
        ERROR() << "Can't parse request: " << ex << " [" << requestBody << "] ";
        return fatalResult("Can't parse request: ");
    } catch (const std::exception& ex) {
        ERROR() << ex.what() <<  " [" << requestBody << "] ";
        return errorResult("Error removing role");
    }
    return successResult();
}

std::string getAllRoles(acl::ACLGateway gw)
{
    try {
        const auto loginsToRoles = gw.allUsersIDMRoles();
        json::Builder builder;
        builder << [&](json::ObjectBuilder builder) {
            builder["code"] = 0;
            builder["users"] << [&](json::ArrayBuilder builder) {
                for (const auto& loginToRoles : loginsToRoles) {
                    const auto& login = loginToRoles.first;
                    const auto& roles = loginToRoles.second;
                    builder << [&](json::ObjectBuilder builder) {
                        builder[LOGIN_PARAM] = login;
                        builder["roles"] << [&](json::ArrayBuilder builder) {
                            for (const auto& role : roles) {
                                builder << [&](json::ObjectBuilder builder) {
                                    builder[SERVICE_SLUG] = role.service;
                                    builder[ROLE_SLUG] = role.role;
                                };
                            }
                        };
                    };
                };
            };
        };
        return builder.str();
    } catch (const std::exception& ex) {
        ERROR() << "getAllRoles: " << ex.what();
        return errorResult("Error retreving all roles");
    }
}

} // namespace maps::wiki::aclsrv::idm
