#include <maps/wikimap/mapspro/services/tasks_social/src/assessment_sampler/lib/load_units/edits.h>

#include <maps/libs/chrono/include/days.h>
#include <maps/wikimap/mapspro/libs/assessment/include/gateway.h>
#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/robot.h>
#include <maps/wikimap/mapspro/libs/revision/include/yandex/maps/wiki/revision/common.h>

#include <maps/wikimap/mapspro/libs/unittest/include/yandex/maps/wiki/unittest/arcadia.h>
#include <maps/wikimap/mapspro/services/tasks_social/src/assessment_sampler/tests/helpers.h>

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


#define CHECK_UNIT_MATCHES_EDIT(unit_, commitId_, createdBy_, createdAt_, action_)\
    do {\
        UNIT_ASSERT_EQUAL(unit_.entity.id, std::to_string(commitId_));\
        UNIT_ASSERT_EQUAL(unit_.entity.domain, assessment::Entity::Domain::Edits);\
        UNIT_ASSERT_EQUAL(unit_.action.by, createdBy_);\
        UNIT_ASSERT_EQUAL(unit_.action.at, createdAt_);\
        UNIT_ASSERT_EQUAL(unit_.action.name, action_);\
    } while (false)

namespace maps::wiki::assessment::sampler::tests {

using namespace std::chrono_literals;

Y_UNIT_TEST_SUITE_F(load_units_edits_tests, unittest::ArcadiaDbFixture) {
    Y_UNIT_TEST(should_collect_unmoderated_edits_during_period) {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        const std::string action = ACTION_OBJECT_MODIFIED;
        const std::vector<TId> commitIds = {
            createEditCommitEvent(*txn, USER, timepointMin - 1s, action),
            createEditCommitEvent(*txn, ANOTHER_USER, timepointMin, action),
            createEditCommitEvent(*txn, USER, timepointMin + 1s, action),
            createEditCommitEvent(*txn, ANOTHER_USER, timepointMax - 1s, action),
            createEditCommitEvent(*txn, USER, timepointMax, action),
            createEditCommitEvent(*txn, ANOTHER_USER, timepointMax + 1s, action)};

        social::Gateway socialGw(*txn);
        auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, {USER, ANOTHER_USER});
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 2);

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-1").size(), 1);
        CHECK_UNIT_MATCHES_EDIT(groupNameToUnits.at("rd_jc-1")[0], commitIds[2], USER, timepointMin + 1s, action);

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-2").size(), 2);
        sortUnits(groupNameToUnits.at("rd_jc-2"));
        CHECK_UNIT_MATCHES_EDIT(groupNameToUnits.at("rd_jc-2")[0], commitIds[1], ANOTHER_USER, timepointMin, action);
        CHECK_UNIT_MATCHES_EDIT(groupNameToUnits.at("rd_jc-2")[1], commitIds[3], ANOTHER_USER, timepointMax - 1s, action);
    }

    Y_UNIT_TEST(should_group_edits_by_category_and_user) {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        createEditCommitEvent(*txn, 1, timepointMin + 1s, ACTION_OBJECT_MODIFIED, "category_a");
        createEditCommitEvent(*txn, 1, timepointMin + 1s, ACTION_OBJECT_MODIFIED, "category_a");
        createEditCommitEvent(*txn, 1, timepointMin + 1s, ACTION_OBJECT_MODIFIED, "category_b");
        createEditCommitEvent(*txn, 2, timepointMin + 1s, ACTION_OBJECT_MODIFIED, "category_a");
        createEditCommitEvent(*txn, 2, timepointMin + 1s, ACTION_OBJECT_MODIFIED, "category_b");
        createEditCommitEvent(*txn, 2, timepointMin + 1s, ACTION_OBJECT_MODIFIED, "category_b");

        social::Gateway socialGw(*txn);
        auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, {1, 2});
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 4);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("category_a-1").size(), 2);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("category_a-2").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("category_b-1").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("category_b-2").size(), 2);
    }

    Y_UNIT_TEST(should_ignore_moderated_edits)
    {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        createNewTask(*txn);

        social::Gateway socialGw(*txn);
        const auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, {USER});
        UNIT_ASSERT(groupNameToUnits.empty());
    }

    Y_UNIT_TEST(should_ignore_unmoderated_edits_by_robots)
    {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        ASSERT(!common::isRobot(USER));
        createEditCommitEvent(*txn, USER, timepointMin + 1s, ACTION_OBJECT_MODIFIED);
        for (const auto robotUid : common::ALL_ROBOTS_UIDS) {
            createEditCommitEvent(*txn, robotUid, timepointMin + 1s, ACTION_OBJECT_MODIFIED);
        }

        auto allowedUids = social::TUids{common::ALL_ROBOTS_UIDS.cbegin(), common::ALL_ROBOTS_UIDS.cend()};
        allowedUids.insert(USER);

        social::Gateway socialGw(*txn);
        const auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, allowedUids);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-1").size(), 1);
        UNIT_ASSERT(!common::isRobot(groupNameToUnits.at("rd_jc-1")[0].action.by));
    }

    Y_UNIT_TEST(should_collect_sample_for_allowed_users_only)
    {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        const std::array commitIds = {
            std::to_string(createEditCommitEvent(*txn, 1, timepointMin + 1s, ACTION_OBJECT_MODIFIED)),
            std::to_string(createEditCommitEvent(*txn, 2, timepointMin + 1s, ACTION_OBJECT_MODIFIED)),
            std::to_string(createEditCommitEvent(*txn, 3, timepointMin + 1s, ACTION_OBJECT_MODIFIED)),
            std::to_string(createEditCommitEvent(*txn, 4, timepointMin + 1s, ACTION_OBJECT_MODIFIED))
        };

        social::Gateway socialGw(*txn);
        const auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, {1, 3});
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 2);

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-1").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-1")[0].entity.id, commitIds[0]);

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-3").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-3")[0].entity.id, commitIds[2]);
    }

    Y_UNIT_TEST(should_ignore_unmoderated_edits_of_service_category_group)
    {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        const auto serviceCategories = serviceGroupCategories();
        ASSERT(serviceCategories.size());

        const auto commitId = createEditCommitEvent(*txn, USER, timepointMin + 1s, ACTION_OBJECT_MODIFIED, RD_JC_CATEGORY);
        for (const auto& serviceCategory : serviceGroupCategories()) {
            createEditCommitEvent(*txn, USER, timepointMin + 1s, ACTION_OBJECT_MODIFIED, serviceCategory);
        }

        social::Gateway socialGw(*txn);
        const auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, {USER});
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-1").size(), 1);
        CHECK_UNIT_MATCHES_EDIT(groupNameToUnits.at("rd_jc-1")[0], commitId, USER, timepointMin + 1s, ACTION_OBJECT_MODIFIED);
    }

    Y_UNIT_TEST(should_ignore_unmoderated_jc_deletion)
    {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        const std::array commitIds = {
            createEditCommitEvent(*txn, 1, timepointMin + 1s, ACTION_OBJECT_DELETED,  RD_JC_CATEGORY),
            createEditCommitEvent(*txn, 1, timepointMin + 1s, ACTION_OBJECT_DELETED,  RD_EL_CATEGORY),
            createEditCommitEvent(*txn, 1, timepointMin + 1s, ACTION_OBJECT_MODIFIED, RD_JC_CATEGORY)
        };

        social::Gateway socialGw(*txn);
        const auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, {1});
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 2);

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_el-1").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_el-1")[0].entity.id, std::to_string(commitIds[1]));

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-1").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_jc-1")[0].entity.id, std::to_string(commitIds[2]));
    }

    Y_UNIT_TEST(should_ignore_non_trunk_commit)
    {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        const auto trunkBranchId = maps::wiki::revision::TRUNK_BRANCH_ID;
        const auto otherBranchId = trunkBranchId + 1;

        const auto trunkEventId = createEditCommitEvent(*txn, USER, timepointMin + 1s, ACTION_OBJECT_MODIFIED, RD_EL_CATEGORY, trunkBranchId);
        createEditCommitEvent(*txn, USER, timepointMin + 1s, ACTION_OBJECT_MODIFIED, RD_EL_CATEGORY, otherBranchId);

        social::Gateway socialGw(*txn);
        const auto groupNameToUnits = loadEditsUnits(socialGw, timepointMin, timepointMax, {USER});
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_el-1").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("rd_el-1")[0].entity.id, std::to_string(trunkEventId));
    }
}

} // maps::wiki::assessment::sampler::tests
