#include <maps/wikimap/mapspro/services/tasks_realtime/src/skills_updater/lib/auto_experts.h>

#include <maps/wikimap/mapspro/libs/acl/include/aclgateway.h>
#include <yandex/maps/wiki/unittest/arcadia.h>

#include <library/cpp/testing/gmock_in_unittest/gmock.h>
#include <library/cpp/testing/unittest/env.h>
#include <library/cpp/testing/unittest/registar.h>


namespace maps::wiki::skills_updater {

using namespace testing;

namespace {

bool isUserAutoExpert(pqxx::transaction_base& txn, const acl::UID uid, const std::string category_group)
{
    const auto user = acl::ACLGateway(txn).user(uid);

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

    return false;
}


bool isUserAddrAutoExpert(pqxx::transaction_base& txn, const size_t uid){
    return isUserAutoExpert(txn, uid, "addr_group");
}


configs::editor::CategoryGroups getCategoryGroups()
{
    const auto categoryGroupsDoc = xml3::Doc::fromFile(
        ArcadiaSourceRoot() + "/maps/wikimap/mapspro/cfg/editor/category_groups.xml"
    );

    return configs::editor::CategoryGroups(
        categoryGroupsDoc.node("/category-groups")
    );
}

}


Y_UNIT_TEST_SUITE_F(autoExperts, unittest::ArcadiaDbFixture) {
    Y_UNIT_TEST(shouldAddExpertRoleForAddrCategory) {
        pqxx::connection conn(connectionString());
        const auto categoryGroups = getCategoryGroups();

        {   // Setup
            pqxx::work txn(conn);

            acl::ACLGateway aclGw(txn);
            aclGw.createUser(1, "user 1", "", 0);
            aclGw.createUser(2, "user 2", "", 0);
            aclGw.createUser(3, "user 3", "", 0);
            aclGw.createRole("expert.auto.addr_group", "");
            aclGw.createRole("expert.addr_group", "");
            aclGw.createGroup("autoexpertise-disabled", "");

            txn.commit();
        }

        {   // Should sum categories in the group
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                // User 1 has more than 500 changes in the addr_group
                "(1, 'addr', 'accept', 200),"
                "(1, 'arrival_point', 'accept', 200),"
                "(1, 'zipcode', 'accept', 200),"
                // User 2 has less than 500 changes in the addr_group
                "(2, 'addr', 'accept', 200),"
                "(2, 'arrival_point', 'accept', 200),"
                "(2, 'bld', 'accept', 200);" // does not belong to addr_gropu
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1, 2});

            EXPECT_TRUE(isUserAddrAutoExpert(txn, 1));
            EXPECT_FALSE(isUserAddrAutoExpert(txn, 2));
        }

        {   // Should consider 'addr' group only
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                // User 1 has more than 500 changes in the addr_group
                "(1, 'addr', 'accept', 600),"
                // User 2 has more than 500 changes in a non addr_group
                "(2, 'bld', 'accept', 600);"
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1, 2});

            EXPECT_TRUE(isUserAddrAutoExpert(txn, 1));
            EXPECT_FALSE(isUserAddrAutoExpert(txn, 2));
            EXPECT_FALSE(isUserAutoExpert(txn, 1, "bld_group"));
            EXPECT_FALSE(isUserAutoExpert(txn, 2, "bld_group"));
        }

        {   // Should consider only users with more or equal than 500 changes in the addr group.
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                // User 1 has less than 500 changes in addr group
                "(1, 'zipcode', 'accept', 499),"
                // User 2 has 500 changes in total
                "(2, 'zipcode', 'accept', 499),"
                "(2, 'zipcode', 'revert', 1),"
                // User 3 has more than 500 changes in total
                "(3, 'bld', 'accept', 1000);"
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1, 2, 3});

            EXPECT_FALSE(isUserAddrAutoExpert(txn, 1));
            EXPECT_TRUE(isUserAddrAutoExpert(txn, 2));
            EXPECT_FALSE(isUserAddrAutoExpert(txn, 3));
        }

        {   // Should consider only users with more or equal than 95% accepted changes in the group.
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                // User 1 has less than 95% accepted changes
                "(1, 'zipcode', 'accept', 949),"
                "(1, 'zipcode', 'edit', 51),"
                // User 2 has more than 95% accepted changes
                "(2, 'zipcode', 'accept', 951),"
                "(2, 'zipcode', 'edit', 49),"
                // User 3 has more than 95% accepted changes but in another group
                "(3, 'rd', 'accept', 1000);"
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1, 2, 3});

            EXPECT_FALSE(isUserAddrAutoExpert(txn, 1));
            EXPECT_TRUE(isUserAddrAutoExpert(txn, 2));
            EXPECT_FALSE(isUserAddrAutoExpert(txn, 3));
        }

        {   // Should not consider users from 'autoexpertise-disabled'.
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                "(1, 'zipcode', 'accept', 1000),"
                "(2, 'zipcode', 'accept', 1000);"
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            aclGw.group("autoexpertise-disabled").add(aclGw.user(2));
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1, 2});

            EXPECT_TRUE(isUserAddrAutoExpert(txn, 1));
            EXPECT_FALSE(isUserAddrAutoExpert(txn, 2));
        }

        {   // Should not do anything if the role 'expert.auto.<category_group>' is absent
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                "(1, 'addr', 'accept', 1000);"
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            aclGw.drop(aclGw.role("expert.auto.addr_group"));
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1});

            EXPECT_FALSE(isUserAddrAutoExpert(txn, 1));
        }

        {  // Should not throw if the user is absent.
            pqxx::work txn(conn);
            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            EXPECT_NO_THROW(
                skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {100})
            );
        }

        {   // Should not throw if the user already an autoexpert
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                "(1, 'addr', 'accept', 1000);"
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1});
            EXPECT_TRUE(isUserAddrAutoExpert(txn, 1));

            EXPECT_NO_THROW(skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1}));
            EXPECT_TRUE(isUserAddrAutoExpert(txn, 1));
        }

        {   // Should make an autoexpert even if the user already an expert
            pqxx::work txn(conn);
            txn.exec(
                "INSERT INTO social.skills "
                "(uid, category_id, resolve_resolution, amount) VALUES "
                "(1, 'addr', 'accept', 1000);"
            );

            social::Gateway socialGw(txn);
            acl::ACLGateway aclGw(txn);

            aclGw.createPolicy(aclGw.user(1), aclGw.role("expert.addr_group"), aclGw.aoi(0));
            skills_updater::makeExperts(socialGw, aclGw, categoryGroups, {1});

            EXPECT_TRUE(isUserAddrAutoExpert(txn, 1));
        }
    }
}

} // maps::wiki::skills_updater
