#include <maps/wikimap/mapspro/services/tasks_social/src/assessment_sampler/lib/load_units/feedback.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/social/include/yandex/maps/wiki/social/feedback/gateway_rw.h>
#include <maps/wikimap/mapspro/libs/social/tests/helpers/fb_task_creator.h>

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

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

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

namespace feedback = maps::wiki::social::feedback;
using unittest::txnNow;
using feedback::tests::FbTaskCreator;
using feedback::TaskState;
using feedback::Type;
using feedback::Workflow;


#define CHECK_ACTION(action_, by_, at_, name_)\
    do {\
        UNIT_ASSERT_EQUAL(action_.by, by_);\
        UNIT_ASSERT_EQUAL(action_.at, at_);\
        UNIT_ASSERT_EQUAL(action_.name, name_);\
    } while(false)


Y_UNIT_TEST_SUITE_F(load_units_feedback_tests, unittest::ArcadiaDbFixture) {
    Y_UNIT_TEST(should_filter_closed_tasks_with_correct_date_only) {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = chrono::getComprisingTimeSpan<chrono::Days>(DATE);

        const std::array taskIds = {
            FbTaskCreator(*txn, TaskState::Accepted).resolvedAt(DATE_STR).create().id(),
            FbTaskCreator(*txn, TaskState::Accepted).resolvedAt(DATE_STR_NEXT).create().id(),
            FbTaskCreator(*txn, TaskState::Accepted).resolvedAt(DATE_STR_PREV).create().id()
        };

        auto closedTasks = feedback::GatewayRW(*txn).tasksByFilter(impl::closedFilter(timepointMin, timepointMax));
        UNIT_ASSERT_EQUAL(closedTasks.size(), 1);
        UNIT_ASSERT_EQUAL(closedTasks[0].id(), taskIds[0]);
    }

    Y_UNIT_TEST(should_filter_tasks_closed_by_not_robots_only) {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = chrono::getComprisingTimeSpan<chrono::Days>(DATE);

        const std::array taskIds = {
            FbTaskCreator(*txn, TaskState::Accepted).resolvedAt(DATE_STR).create().id(),
            FbTaskCreator(*txn, TaskState::Accepted).resolvedAt(DATE_STR).uid(common::ROBOT_UID).create().id()
        };

        auto closedTasks = feedback::GatewayRW(*txn).tasksByFilter(impl::closedFilter(timepointMin, timepointMax));
        UNIT_ASSERT_EQUAL(closedTasks.size(), 1);
        UNIT_ASSERT_EQUAL(closedTasks[0].id(), taskIds[0]);
    }

    Y_UNIT_TEST(should_filter_needinfo_tasks_with_correct_date_only) {
        auto txn = pool().masterWriteableTransaction();
        const auto [timepointMin, timepointMax] = chrono::getComprisingTimeSpan<chrono::Days>(DATE);

        const std::array taskIds = {
            FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi-samsara").createdAt(DATE_STR).create().id(),
            FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi-samsara").createdAt(DATE_STR_NEXT).create().id(),
            FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi-samsara").createdAt(DATE_STR_PREV).create().id()
        };

        auto needInfoTasks = feedback::GatewayRW(*txn).tasksByFilter(impl::needInfoFilter(timepointMin, timepointMax));
        UNIT_ASSERT_EQUAL(needInfoTasks.size(), 1);
        UNIT_ASSERT_EQUAL(needInfoTasks[0].id(), taskIds[0]);
    }

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

        *FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi").type(Type::Fence).uid(1);
        *FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi").type(Type::Fence).uid(1);
        *FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi").type(Type::Fence).uid(2);
        *FbTaskCreator(*txn, TaskState::Accepted).source("fbapi").type(Type::Other).uid(2);
        *FbTaskCreator(*txn, TaskState::Accepted).source("fbapi").type(Type::Other).uid(2);
        *FbTaskCreator(*txn, TaskState::Accepted).source("fbapi").type(Type::Fence).uid(1);

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, {1, 2}, HypothesesMode::No);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 4);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("fence-need-info-1").size(), 2);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("fence-need-info-2").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("other-accept-2").size(), 2);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("fence-accept-1").size(), 1);
    }

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

        *FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi");
        *FbTaskCreator(*txn, TaskState::Incoming).source("fbapi");
        *FbTaskCreator(*txn, TaskState::Accepted).source("fbapi");

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, BOTH_USERS, HypothesesMode::No);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 2);

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("road-need-info-1").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("road-need-info-1")[0].action.name, ACTION_NEED_INFO);

        UNIT_ASSERT_EQUAL(groupNameToUnits.at("road-accept-1").size(), 1);
        UNIT_ASSERT_EQUAL(groupNameToUnits.at("road-accept-1")[0].action.name, ACTION_ACCEPT);
    }

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

        *FbTaskCreator(*txn).source(SOURCE_TASK);
        *FbTaskCreator(*txn, TaskState::Accepted).source(SOURCE_TASK);

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, BOTH_USERS, HypothesesMode::Yes);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(units[0].entity.domain, Entity::Domain::Feedback);
        UNIT_ASSERT_EQUAL(units[0].action.name, ACTION_ACCEPT);
    }

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

        std::array taskIds = {
            FbTaskCreator(*txn, TaskState::Accepted).source("fbapi").create().id(),
            FbTaskCreator(*txn, TaskState::Accepted).source("mrc").create().id()
        };

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, BOTH_USERS, HypothesesMode::No);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(
            units[0].entity,
            Entity({std::to_string(taskIds[0]), Entity::Domain::Feedback})
        );
    }

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

        std::array taskIds = {
            FbTaskCreator(*txn, TaskState::Accepted)
                .source(SOURCE_TASK).create().id(),
            FbTaskCreator(*txn, TaskState::Accepted)
                .create().id()
        };

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, BOTH_USERS, HypothesesMode::No);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(
            units[0].entity,
            Entity({std::to_string(taskIds[1]), Entity::Domain::Feedback})
        );
    }

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

        std::array taskIds = {
            FbTaskCreator(*txn, TaskState::Accepted)
                .source(SOURCE_TASK).create().id(),
            FbTaskCreator(*txn, TaskState::Accepted)
                .create().id()
        };

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, BOTH_USERS, HypothesesMode::Yes);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(
            units[0].entity,
            Entity({std::to_string(taskIds[0]), Entity::Domain::Feedback})
        );
    }

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

        std::array taskIds = {
            FbTaskCreator(*txn, TaskState::Accepted).source("non-experiment-foo").create().id(),
            FbTaskCreator(*txn, TaskState::Accepted).source("experiment-foo").create().id()
        };

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, BOTH_USERS, HypothesesMode::Yes);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(
            units[0].entity,
            Entity({std::to_string(taskIds[0]), Entity::Domain::Feedback})
        );
    }

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

        *FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi").uid(USER);
        *FbTaskCreator(*txn, TaskState::NeedInfo).source("fbapi").uid(ANOTHER_USER);

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, {USER}, HypothesesMode::No);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(units[0].entity.domain, Entity::Domain::Feedback);
        CHECK_ACTION(units[0].action, USER, now, ACTION_NEED_INFO);
    }

    Y_UNIT_TEST(should_collect_hypoteses_sample_for_allowed_uids)
    {
        auto txn = pool().masterWriteableTransaction();
        feedback::GatewayRW feedbackGateway(*txn);
        const auto now = txnNow(*txn);
        const auto [timepointMin, timepointMax] = txnNowSpan(*txn);

        *FbTaskCreator(*txn, TaskState::Accepted).source(SOURCE_TASK).uid(USER);
        *FbTaskCreator(*txn, TaskState::Accepted).source(SOURCE_TASK).uid(ANOTHER_USER);

        feedback::GatewayRO fbGw{*txn};
        const auto groupNameToUnits = loadFeedbackUnits(fbGw, timepointMin, timepointMax, {USER}, HypothesesMode::Yes);
        UNIT_ASSERT_EQUAL(groupNameToUnits.size(), 1);

        auto units = groupNameToUnits.cbegin()->second;
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(units.size(), 1);
        UNIT_ASSERT_EQUAL(units[0].entity.domain, Entity::Domain::Feedback);
        CHECK_ACTION(units[0].action, USER, now, ACTION_ACCEPT);
    }
}

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