#include "../include/cached_moderation_statuses.h"

#include <maps/libs/enum_io/include/enum_io_fwd.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/libs/acl/include/aclgateway.h>
#include <maps/wikimap/mapspro/libs/acl_utils/include/moderation.h>
#include <maps/libs/common/include/exception.h>

#include <mutex>
#include <utility>
#include <vector>

namespace maps::wiki::acl_utils {

// clang-format off
constexpr maps::enum_io::Representations<UserGroup> USER_GROUP_REPRESENTATION {
    {UserGroup::Robot,           "robot"},
    {UserGroup::Cartographer,    "cartographer"},
    {UserGroup::YandexModerator, "yandex-moderator"},
    {UserGroup::Outsourcer,      "outsource-role"}
};
// clang-format on
DEFINE_ENUM_IO(UserGroup, USER_GROUP_REPRESENTATION);

UserGroupUidsCache::UserGroupUidsCache(
    UserGroup userGroup,
    pgpool3::Pool& aclPool,
    std::chrono::minutes updatePeriod)
    : userGroup_(userGroup)
    , aclPool_(aclPool)
    , dataUpdater_(
          [this]() { updateDataThreadSafeNoExcept(); },
          std::chrono::minutes(updatePeriod))
{
    updateUids();
    dataUpdater_.startDelayed();
}

UidsPtr UserGroupUidsCache::uids() const
{
    std::shared_lock lock(readWriteLock_);
    return uidsPtr_;
}

UidsPtr UserGroupUidsCache::fetchUids()
{
    auto txn = aclPool_.slaveTransaction();
    auto aclGateway = acl::ACLGateway(txn.get());

    std::vector<acl::UID> userGroupUids;
    switch (userGroup_) {
        case UserGroup::Outsourcer:
            userGroupUids = acl_utils::outsourcerUids(aclGateway);
            break;
        default:
            auto moderationStatus = std::string(toString(userGroup_));
            userGroupUids = acl_utils::uidsByModerationStatus(
                aclGateway, moderationStatus);
    }

    if (userGroupUids.empty()) {
        WARN() << "No uids for '" << userGroup_ << "' user group found.";
    }

    return std::make_shared<std::vector<acl::UID>>(std::move(userGroupUids));
}

void UserGroupUidsCache::updateUids()
{
    INFO() << "Started updating uids for '" << userGroup_ << "' cache.";
    auto updatedUids = fetchUids();
    INFO() << "Fetched " << updatedUids->size() << " uids for '" << userGroup_ << "' cache.";
    std::unique_lock lock(readWriteLock_);
    std::swap(uidsPtr_, updatedUids);
    INFO() << "Finished fetching uids for '" << userGroup_ << "' cache.";
}

void UserGroupUidsCache::updateDataThreadSafeNoExcept()
{
    static const std::string message =
        "Failed updating uids cache for user group '";

    try {
        updateUids();
    } catch (maps::Exception& ex) {
        ERROR() << message << userGroup_ << "': " << ex;
    } catch (std::exception& ex) {
        ERROR() << message << userGroup_ << "': " << ex.what();
    }
}

} // namespace maps::wiki::acl_utils
