#include <maps/wikimap/mapspro/libs/acl/include/group.h>

#include <maps/wikimap/mapspro/libs/acl/include/aclgateway.h>
#include <maps/wikimap/mapspro/libs/acl/include/common.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <maps/wikimap/mapspro/libs/acl/include/policy.h>
#include <maps/wikimap/mapspro/libs/acl/include/user.h>

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

namespace maps::wiki::acl {

namespace {

const std::string QUERY_POLICIES =
    "SELECT * FROM acl.policy"
    " JOIN acl.role ON (policy.role_id = role.id)"
    " WHERE agent_id = ";

} // namespace

Group::Group(
    ID id,
    const std::string& name,
    const std::string& description,
    Transaction& work)
    : id_(id)
    , name_(name)
    , description_(description)
    , work_(work)
{
}

void
Group::setName(const std::string& newName)
{
    try {
        if (newName.empty()) {
            throw BadParam() << "Empty group new name";
        }
        auto query =
            "UPDATE acl.group SET name = " + work_.quote(newName) +
            " WHERE id = " + std::to_string(id_);
        work_.exec(query);
        name_ = newName;
    } catch (const pqxx::unique_violation& ex) {
        throw DuplicateGroup() << "Group with name " << newName
            << " already exists";
    } CATCH_CONVERT();
}

void
Group::setDescription(const std::string& newDescription)
{
    try {
        work_.exec(
            "UPDATE acl.group SET description = " +
            work_.quote(newDescription) +
            " WHERE id = " + std::to_string(id_));
        description_ = newDescription;
    } CATCH_CONVERT();
}

void
Group::add(const User& user) const
{
    try {
        auto query =
            "INSERT INTO acl.group_user(group_id, user_id) VALUES "
            "(" + std::to_string(id_) + ", " + std::to_string(user.id()) + ")";
        work_.exec(query);
    } catch (const pqxx::unique_violation& ex) {
        throw DuplicateRelation() << "User " << user.login()
            << " already exists in group " << name();
    } CATCH_CONVERT();
}

void
Group::remove(const User& user) const
{
    try {
        auto query =
            "DELETE FROM acl.group_user"
            " WHERE group_id = " + std::to_string(id_) +
            "   AND user_id = " + std::to_string(user.id());
        work_.exec(query);
    } CATCH_CONVERT();
}

std::vector<User>
Group::users() const
{
    ACLGateway aclGw(work_);
    return aclGw.users(id(), 0, 0, std::nullopt, 0, 0).value();
}

std::vector<Policy>
Group::policies() const
{
    auto rows = work_.exec(QUERY_POLICIES + std::to_string(id_));

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

void
Group::removePolicy(ID roleID, ID aoiID) const
{
    auto query =
        "DELETE FROM acl.policy "
        " WHERE agent_id = " + std::to_string(id_) +
        " AND role_id = " + std::to_string(roleID) +
        " AND aoi_id = " + std::to_string(aoiID);
    work_.exec(query);
}

void
Group::removePolicies() const
{
    auto query =
        "DELETE FROM acl.policy WHERE agent_id = " + std::to_string(id_);
    work_.exec(query);
}

bool
isExistingGroup(
    const std::vector<acl::Group>& groups,
    acl::ID groupId)
{
    return std::any_of(
        groups.begin(),
        groups.end(),
        [&](const auto& group) {
            return group.id() == groupId;
        });
}

} // namespace maps::wiki::acl
