#include <maps/wikimap/mapspro/libs/acl_utils/include/moderation.h>

#include <maps/wikimap/mapspro/libs/acl/include/policy.h>
#include <maps/wikimap/mapspro/libs/acl/include/role.h>
#include <yandex/maps/wiki/common/moderation.h>

namespace maps::wiki::acl_utils {

namespace {

const size_t ACL_BATCH_SIZE = 10000;

// Ordered by descending significance
const std::vector<std::string> MODERATION_STATUSES = {
    common::MODERATION_STATUS_ROBOT,
    common::MODERATION_STATUS_CARTOGRAPHER,
    common::MODERATION_STATUS_YANDEX_MODERATOR,
    common::MODERATION_STATUS_MODERATOR,
    common::MODERATION_STATUS_EXPERT,
    common::MODERATION_STATUS_COMMON
};

const std::set<std::string> MAIN_MODERATION_STATUSES = {
    common::MODERATION_STATUS_MODERATOR,
    common::MODERATION_STATUS_YANDEX_MODERATOR,
    common::MODERATION_STATUS_CARTOGRAPHER
};

} // unnamed namespace

std::vector<std::string> moderationStatuses()
{
    return MODERATION_STATUSES;
}

bool hasRole(const std::vector<acl::Policy>& policies, const std::string& roleName)
{
    return std::any_of(
        policies.begin(), policies.end(),
        [&](const auto& policy) {
            return policy.role().name() == roleName;
        }
    );
}

bool hasRole(const acl::User& user, const std::string& roleName)
{
    return hasRole(user.allPolicies(), roleName);
}

std::string
moderationStatus(const acl::ACLGateway& aclGtw, const acl::User& user)
{
    return aclGtw.firstApplicableRole(
        user,
        MODERATION_STATUSES,
        common::MODERATION_STATUS_COMMON);
}

std::map<std::string, std::vector<acl::UID>>
moderationStatusToUids(acl::ACLGateway& gateway, const std::vector<acl::User>& users)
{
    std::map<std::string, std::vector<acl::UID>> modStatus2Uids;

    auto batchBegin = std::begin(users);
    while (batchBegin != std::end(users)) {
        auto batchEnd = std::next(
            batchBegin,
            std::min<size_t>(
                ACL_BATCH_SIZE, std::distance(batchBegin, std::end(users))));

        std::vector<acl::User> batch(batchBegin, batchEnd);
        for (const auto& [uid, modStatus]: moderationStatuses(gateway, batch)) {
            modStatus2Uids[modStatus].push_back(uid);
        }
        batchBegin = batchEnd;
    }
    return modStatus2Uids;
}

std::vector<acl::UID>
uidsByModerationStatus(acl::ACLGateway& aclGateway, const std::string& modStatus)
{
    auto roles = aclGateway.roles(acl::InclusionType::StartsWith, modStatus);
    std::set<acl::ID> rolesIds;
    const auto modStatusPrefix = modStatus + common::MODERATION_STATUS_DELIMITER;
    for (const auto& role: roles) {
        const auto& roleName = role.name();
        if (roleName == modStatus || roleName.starts_with(modStatusPrefix)) {
            rolesIds.insert(role.id());
        }
    }

    auto usersIds = aclGateway.userIdsByRoles(rolesIds);
    auto users = aclGateway.usersByIds({usersIds.begin(), usersIds.end()});

    auto modStatus2Uids = moderationStatusToUids(aclGateway, users);
    return modStatus2Uids[modStatus];
}

std::map<acl::UID, std::string>
moderationStatuses(acl::ACLGateway& aclGtw, const std::set<acl::UID>& userIds)
{
    auto users = aclGtw.users(std::vector<acl::UID>{userIds.begin(), userIds.end()});
    return moderationStatuses(aclGtw, users);
}

std::map<acl::UID, std::string>
moderationStatuses(acl::ACLGateway& aclGtw, const std::vector<acl::User>& users)
{
    auto agentIdToStatus = aclGtw.firstApplicableRoles(
        users,
        MODERATION_STATUSES,
        common::MODERATION_STATUS_COMMON);

    std::map<acl::UID, std::string> uidToStatus;
    for (const auto& user : users) {
        if (agentIdToStatus.count(user.id())) {
            uidToStatus[user.uid()] = agentIdToStatus[user.id()];
        }
    }
    return uidToStatus;
}

bool isPieceworker(const std::vector<acl::Policy>& policies)
{
    return hasRole(policies, common::PIECEWORK_ROLE);
}

bool isOutsourcer(const std::vector<acl::Policy>& policies)
{
    return hasRole(policies, common::OUTSOURCE_ROLE);
}

bool isOutsourcer(const acl::User& user)
{
    return isOutsourcer(user.allPolicies());
}

std::vector<acl::UID> outsourcerUids(acl::ACLGateway& gateway)
{
    acl::ID groupId = 0;
    auto roleId = gateway.role(common::OUTSOURCE_ROLE).id();
    acl::ID aoiId = 0;
    std::optional<acl::User::Status> userStatus = std::nullopt;
    size_t page = 0; // Pager returns all results when ask for page 0.
    size_t perPage = 0;
    const auto users =
        gateway.users(groupId, roleId, aoiId, userStatus, page, perPage)
            .value();

    std::vector<acl::UID> uids;
    uids.reserve(users.size());
    for (const auto& user : users) {
        uids.push_back(user.uid());
    }
    return uids;
}

bool
isRobot(const std::string& moderationStatus)
{
    return moderationStatus == common::MODERATION_STATUS_ROBOT;
}

bool
isCartographer(const std::string& moderationStatus)
{
    return moderationStatus == common::MODERATION_STATUS_CARTOGRAPHER;
}

bool
isCartographer(const acl::ACLGateway& aclGtw, const acl::User& user)
{
    return isCartographer(moderationStatus(aclGtw, user));
}

bool
isYandexModerator(const std::string& moderationStatus)
{
    return moderationStatus == common::MODERATION_STATUS_YANDEX_MODERATOR;
}

bool
isModerator(const std::string& moderationStatus)
{
    return MAIN_MODERATION_STATUSES.count(moderationStatus);
}

bool
isModerator(const acl::ACLGateway& aclGtw, const acl::User& user)
{
    auto status = moderationStatus(aclGtw, user);
    return isModerator(status);
}

std::string createdOrUnbannedAt(const acl::User& user)
{
    const auto& unbannedAt = user.unbannedAt();
    if (unbannedAt) {
        return chrono::formatSqlDateTime(*unbannedAt);
    }
    return user.created();
}

} // namespace maps::wiki::acl_utils
