#include "configuration.h"
#include "idm/idm_service.h"

#include <maps/infra/yacare/include/yacare.h>
#include <maps/infra/yacare/include/params/tvm.h>
#include <maps/libs/json/include/builder.h>

namespace maps::fw_updater::storage {

namespace {

const std::string TVM_ALIAS = "maps-core-firmware-storage";

void requireIdmTvmId(auth::TvmId srcTvmId)
{
    if (srcTvmId != configuration()->idmTvmId()) {
        throw yacare::errors::Forbidden() << "Only IDM may perform this request";
    }
}

void logIdmRequestId(const yacare::Request& request)
{
    const auto& idmRequestId = request.env("HTTP_X_IDM_REQUEST_ID");
    INFO() << "X-IDM-Request-Id: " << idmRequestId;
}

void handleBadParameter(yacare::Response& response,
                        const std::string& errorMessage)
{
    response.setStatus(yacare::HTTPStatus::BadRequest);
    response << YCR_JSON(obj) {
        obj["code"] = 1;
        obj["fatal"] = errorMessage;
    };
}

struct UserRoleParams {
    std::string login;
    std::string path;
};

UserRoleParams parseUserRoleParams(const std::string& body)
{
    yacare::QueryParams paramsMap;
    yacare::parseUrlencodedBody(body, &paramsMap);

    auto loginIt = paramsMap.find("login");
    if (loginIt == paramsMap.end()) {
        throw idm::BadParameter() << "missing parameter `login`";
    }

    auto pathIt = paramsMap.find("path");
    if (pathIt == paramsMap.end()) {
        throw idm::BadParameter() << "missing parameter `path`";
    }

   return UserRoleParams{
       .login = loginIt->second.front(),
       .path = pathIt->second.front()
   };
}

} // namespace

YCR_RESPOND_TO("GET /v2/idm/info/", tvmId,
               YCR_USING(yacare::Tvm2ServiceRequire(TVM_ALIAS)))
{
    requireIdmTvmId(tvmId);
    logIdmRequestId(request);

    auto txn = configuration()->pool().slaveTransaction();
    auto roleTree = idm::getRoleTree(*txn);

    response << YCR_JSON(obj) {
        obj["code"] = 0;
        roleTree.toJson(obj);
    };
}


YCR_RESPOND_TO("POST /v2/idm/add-role/", tvmId,
               YCR_USING(yacare::Tvm2ServiceRequire(TVM_ALIAS)))
{
    requireIdmTvmId(tvmId);
    logIdmRequestId(request);

    try {
        auto params = parseUserRoleParams(request.body());

        auto txn = configuration()->pool().masterWriteableTransaction();
        idm::addUserRole(*txn, params.login, idm::parseSlugPath(params.path));
        txn->commit();

        response << YCR_JSON(obj) {
            obj["code"] = 0;
        };
    } catch (const idm::BadParameter& e) {
        handleBadParameter(response, e.what());
    }
}


YCR_RESPOND_TO("POST /v2/idm/remove-role/", tvmId,
               YCR_USING(yacare::Tvm2ServiceRequire(TVM_ALIAS)))
{
    requireIdmTvmId(tvmId);
    logIdmRequestId(request);

    try {
        auto params = parseUserRoleParams(request.body());

        auto txn = configuration()->pool().masterWriteableTransaction();
        idm::removeUserRole(*txn, params.login, idm::parseSlugPath(params.path));
        txn->commit();

        response << YCR_JSON(obj) {
            obj["code"] = 0;
        };
    } catch (const idm::BadParameter& e) {
        handleBadParameter(response, e.what());
    }
}


YCR_RESPOND_TO("GET /v2/idm/get-all-roles/", tvmId,
               YCR_USING(yacare::Tvm2ServiceRequire(TVM_ALIAS)))
{
    requireIdmTvmId(tvmId);
    logIdmRequestId(request);

    auto txn = configuration()->pool().slaveTransaction();
    auto loginToRolesMap = idm::getAllRoles(*txn);

    response << YCR_JSON(obj) {
        obj["code"] = 0;
        obj["users"] << [&](json::ArrayBuilder b) {
            for (const auto& pair : loginToRolesMap) {
                auto& login = pair.first;
                auto& paths = pair.second;
                b << [&](json::ObjectBuilder b) {
                    b["login"] = login;
                    b["roles"] << [&](json::ArrayBuilder b) {
                        for (const auto& path : paths) {
                            b << [&](json::ObjectBuilder b) {
                                for (const auto& pathElem : path) {
                                    b[pathElem.slug] = pathElem.key;
                                }
                            };
                        }
                    };
                };
            }
        };
    };
}

} //namespace maps::fw_updater::storage
