#include "auto_experts.h"
#include "auto_experts_pred.h"

#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/wiki/common/batch.h>

#include <pqxx/pqxx>

#include <functional>
#include <map>
#include <optional>
#include <string>
#include <vector>

namespace maps::wiki::skills_updater {

namespace {

using Groups = std::vector<acl::Group>;

const auto AUTOEXPERTISE_DISABLED_ACL_GROUP = "autoexpertise-disabled";
const auto AUTOEXPERT_ROLE_PREFIX = "expert.auto.";


bool
isAutoExpertiseDisabledForCategoryGroup(
    const std::vector<acl::Role>& allRoles,
    const std::string& categoryGroup)
{
    const auto autoExpertRole = AUTOEXPERT_ROLE_PREFIX + categoryGroup;

    for (const auto& role: allRoles) {
        if (role.name() == autoExpertRole) {
            return false;
        }
    }

    return true;
}


bool
isAutoExpertiseDisabledForUser(const Groups& userGroups)
{
    for (const auto& group: userGroups) {
        if (group.name() == AUTOEXPERTISE_DISABLED_ACL_GROUP) {
            return true;
        }
    }

    return false;
}


bool
isAutoExpert(
    const acl::User& user,
    const std::string& categoryGroup)
{
    const auto autoExpertRoleName = AUTOEXPERT_ROLE_PREFIX + categoryGroup;

    for (const auto& policy: user.allPolicies()) {
        if (policy.role().name() == autoExpertRoleName) {
            return true;
        }
    }

    return false;
}


void
addAutoExpertRole(
    acl::ACLGateway& aclGw,
    const acl::User& user,
    const std::string& categoryGroup)
{
    const auto userGroups = user.groups();

    if (isAutoExpertiseDisabledForUser(userGroups)) {
        INFO() << "Auto expertise is disabled for user '" << user.login() << "'. "
               << "However, the user meets expectations to be an expert in category group '"
               << categoryGroup << "'.";
        return;
    }

    if (isAutoExpert(user, categoryGroup)) {
        return;
    }
    const auto NO_AOI = aclGw.aoi(0);
    aclGw.createPolicy(user, aclGw.role(AUTOEXPERT_ROLE_PREFIX + categoryGroup), NO_AOI);

    INFO() << "User '" << user.login() << "' made an autoexpert in the group '"
           << categoryGroup << "'.";
}


void
makeExpertIfNeeded(
    acl::ACLGateway& aclGw,
    const configs::editor::CategoryGroups& categoryGroups,
    const social::Skills& skills,
    const acl::User& user)
{
    const auto allRoles = aclGw.roles();

    for (const auto& [categoryGroup, predicate]: PREDICATES_BY_CATEGORY_GROUPS) {
        if (isAutoExpertiseDisabledForCategoryGroup(allRoles, categoryGroup)) {
            continue;
        }

        switch (predicate(categoryGroups, skills)) {
            case Action::Add:
                addAutoExpertRole(aclGw, user, categoryGroup);
                break;
            case Action::Remove:
                // removeAutoExpertRole(aclGw, user, categoryGroup);
                break;
            case Action::None: ; // Do nothing
        }
    }
}

} // namespace


void
makeExperts(
    social::Gateway& socialGw,
    acl::ACLGateway& aclGw,
    const configs::editor::CategoryGroups& categoryGroups,
    const std::vector<social::TUid>& uids)
{
    const auto BATCH_SIZE = 1000;

    INFO() << "Expert roles calculation has started.";

    common::applyBatchOp(
        uids, BATCH_SIZE,
        [&](const auto& uidsBatch) {
            for (const auto& skillsByUid: socialGw.getSkills(uidsBatch)) {
                const auto& [uid, skills] = skillsByUid;
                makeExpertIfNeeded(aclGw, categoryGroups, skills, aclGw.user(uid));
            }
        }
    );

    INFO() << "Expert roles calculation has finished.";
}

} // maps::wiki::skills_updater
