#include <maps/wikimap/mapspro/libs/acl/include/aclgateway.h>
#include <maps/wikimap/mapspro/libs/acl/include/permission.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <yandex/maps/wiki/common/pg_utils.h>

#include "exception_helper.h"
#include "factory.h"

namespace maps::wiki::acl {

Permission::Permission(ID id, const std::string& name, ID parentId, Transaction& work)
    : id_(id)
    , name_(name)
    , parentId_(parentId)
    , work_(work)
{
}

Permission
Permission::childPermission(const std::string& childName) const
{
    auto r =
        work_.get().exec(
            "SELECT id FROM acl.permission "
            "WHERE parent_id = " + std::to_string(id_) +
            " AND name = " + work_.get().quote(childName));

    if (r.empty()) {
        throw PermissionNotExists() << "Permission " << id_
            << " doesn't have child with name " << childName;
    }
    return Factory::permission(r[0][0].as<ID>(), childName, id_, work_);
}

Permission
Permission::createChildPermission(const std::string& newName) const
{
    ID newId = 0;
    try {
        auto query =
            "INSERT INTO acl.permission(name, parent_id) VALUES "
                "(" + work_.get().quote(newName) + ", " + std::to_string(id_) + ")"
            " RETURNING id";
        auto r = work_.get().exec(query);
        newId = r[0][0].as<ID>();
    } catch (const pqxx::unique_violation& ex) {
        throw DuplicatePermission() << "Child permission with name " << newName
            << " and parent " << name_
            << " already exists.";
    } CATCH_CONVERT();

    return Factory::permission(newId, newName, id_, work_);
}

std::vector<Role>
Permission::roles() const
{
    pqxx::result rows;
    try {
        rows = work_.get().exec(
            "SELECT r.* FROM acl.role r, acl.role_permission rp "
            "WHERE r.id = role_id AND permission_id = " +
            std::to_string(id_));
    } CATCH_CONVERT();

    std::vector<Role> roles;
    roles.reserve(rows.size());
    for (const auto& row : rows) {
        roles.push_back(Factory::role(
            row["id"].as<ID>(),
            row["name"].as<std::string>(),
            row["description"].as<std::string>(),
            Role::privacy(row["is_public"].as<bool>()),
            work_));
    }
    return roles;
}

std::vector<ID>
Permission::leafIds() const
{
    try {
        auto row = work_.get().exec(
            "SELECT leaf_ids FROM acl.permission "
            "WHERE id = " + std::to_string(id_));
        ASSERT(row.size() == 1);
        REQUIRE(!row[0][0].is_null(),
            "Permissions leaf data out of sync");
        return common::parseSqlArray<ID>(row[0][0].c_str());
    } CATCH_CONVERT();
    return {};
}

void
Permission::updateLeafsIds(ACLGateway& gw)
{
    try {
        std::vector<ID> leafIds;
        auto childRows = work_.get().exec(
            "SELECT id, name FROM acl.permission "
            "WHERE parent_id = " + std::to_string(id_));
        if (childRows.empty()) {
            leafIds = {id_};
        } else {
            for (const auto& childRow : childRows) {
                auto child = Factory::permission(
                    childRow[0].as<ID>(),
                    childRow[1].c_str(),
                    id_,
                    work_);
                child.updateLeafsIds(gw);
                const auto childLeafIds = child.leafIds();
                leafIds.insert(leafIds.end(), childLeafIds.begin(), childLeafIds.end());
            }
        }
        work_.get().exec(
            "UPDATE acl.permission SET leaf_ids = ARRAY[" +
            common::join(leafIds, ',') +
            "] WHERE id = " + std::to_string(id_));
    } CATCH_CONVERT();
}

} // namespace maps::wiki::acl
