#include <library/cpp/testing/common/env.h>
#include <library/cpp/testing/gtest/gtest.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/toloka_manager_cron_jobs/lib/include/detection.h>

namespace maps {
namespace mrc {
namespace toloka {
namespace test {

namespace {

const std::string TS_1 = "task-suite-id-1";
const std::string TS_2 = "task-suite-id-2";

const std::string POOL_ID = "1";

const std::string USER_1 = "user-id-1";
const std::string USER_2 = "user-id-2";
const std::string USER_3 = "user-id-3";

const std::string SOURCE_1 = "source-1";
const std::string SOURCE_2 = "source-2";
const std::string SOURCE_3 = "source-3";
const std::string SOURCE_4 = "source-4";

const auto ACCEPTED = io::AssignmentStatus::Accepted;

constexpr size_t OVERLAP = 3;

} // anonymous namespace

TEST(detection_tests, merge_task_suites)
{
    Detection::IdToTaskSuite idToTaskSuite{
        {TS_1, {TS_1, POOL_ID, OVERLAP, {{SOURCE_1}, {SOURCE_2}}}},
        {TS_2, {TS_2, POOL_ID, OVERLAP, {{SOURCE_3}, {SOURCE_4}}}}};

    Detection::TaskSuiteIdToResults taskSuiteIdToResults{
        // 3 results for task suite 1:
        {
            TS_1,
            {
                {// Result from user 1
                    TS_1,
                    "unused",
                    ACCEPTED,
                    USER_1,
                    {Detection::TaskInput{SOURCE_1}, Detection::TaskInput{SOURCE_2}},
                    {
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.01, 0.01),
                                    geolib3::Point2(0.02, 0.02)
                                ),
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.03, 0.03),
                                    geolib3::Point2(0.05, 0.05)
                                ),
                            })
                        },
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.10, 0.20),
                                    geolib3::Point2(0.15, 0.26)
                                ),
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.3, 0.3),
                                    geolib3::Point2(0.4, 0.4)
                                ),
                            })
                        },
                    },
                },
                {// Result from user 2
                    TS_1,
                    "unused",
                    ACCEPTED,
                    USER_2,
                    {Detection::TaskInput{SOURCE_1}, Detection::TaskInput{SOURCE_2}},
                    {
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.01, 0.011),
                                    geolib3::Point2(0.02, 0.021)
                                ),
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.029, 0.029),
                                    geolib3::Point2(0.052, 0.052)
                                ),
                            })
                        },
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.105, 0.205),
                                    geolib3::Point2(0.145, 0.255)
                                ),
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.35, 0.35),
                                    geolib3::Point2(0.45, 0.45)
                                ),
                            })
                        }
                    },
                },
                {// Result from user 3
                    TS_1,
                    "unused",
                    ACCEPTED,
                    USER_3,
                    {Detection::TaskInput{SOURCE_1}, Detection::TaskInput{SOURCE_2}},
                    {
                        Detection::TaskOutput{
                            DetectionResult::IsEmpty,
                            std::vector<geolib3::BoundingBox>({})
                        },
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.095, 0.195),
                                    geolib3::Point2(0.145, 0.261)
                                ),
                            })
                        }
                    }
                }
            }
        },
        // Results for task suite 2 (only 2 users accomplished)
        {
            TS_2,
            {
                {
                    TS_2,
                   "unused",
                   ACCEPTED,
                   USER_1,
                   {Detection::TaskInput{SOURCE_3}, Detection::TaskInput{SOURCE_4}},
                   {
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.050, 0.050),
                                    geolib3::Point2(0.150, 0.150)
                                )
                            })
                        },
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.100, 0.100),
                                    geolib3::Point2(0.300, 0.300)
                                )
                            })
                        }
                    }
                },
                {
                    TS_2,
                   "unused",
                   ACCEPTED,
                   USER_2,
                   {Detection::TaskInput{SOURCE_3}, Detection::TaskInput{SOURCE_4}},
                   {
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.050, 0.050),
                                    geolib3::Point2(0.150, 0.150)
                                )
                            })
                        },
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.100, 0.100),
                                    geolib3::Point2(0.300, 0.300)
                                )
                            })
                        }
                    }
                }
            }
        }
    };

    auto tsResults = mergeTasksResults<Detection>(idToTaskSuite, taskSuiteIdToResults);

    // Validate results.
    // Aggregated results are available only for task suite 1, but not for
    // task suite 2, where not enough users have accomplished the assignments

    EXPECT_EQ(tsResults.size(), 1u);
    const auto& suiteResult = tsResults.begin()->second;
    const auto& taskResults = suiteResult.taskResults;

    auto findTaskResult = [&](const std::string& src) {
        return std::find_if(
            taskResults.begin(), taskResults.end(),
            [&](const Detection::TaskResult& tr) { return tr.input.imageUrl() == src; });
    };

    auto task1Itr = findTaskResult(SOURCE_1);
    auto task2Itr = findTaskResult(SOURCE_2);

    EXPECT_TRUE(task1Itr != taskResults.end());
    EXPECT_TRUE(task1Itr->output.bboxes().size() == 2);
    EXPECT_EQ(
        task1Itr->output.bboxes()[0],
        geolib3::BoundingBox(
            geolib3::Point2(0.010, 0.011),
            geolib3::Point2(0.020, 0.021)
        )
    );
    EXPECT_EQ(
        task1Itr->output.bboxes()[1],
        geolib3::BoundingBox(
            geolib3::Point2(0.030, 0.030),
            geolib3::Point2(0.052, 0.052)
        )
    );

    EXPECT_TRUE(task2Itr != taskResults.end());
    EXPECT_TRUE(task2Itr->output.bboxes().size() == 1);

    EXPECT_EQ(
        task2Itr->output.bboxes()[0],
        geolib3::BoundingBox(
            geolib3::Point2(0.100, 0.200),
            geolib3::Point2(0.145, 0.260)
        )
    );
}

TEST(detection_tests, merge_task_suites_2)
{
    Detection::IdToTaskSuite idToTaskSuite{
        {TS_1, {TS_1, POOL_ID, OVERLAP, {{SOURCE_1}, {SOURCE_2}}}}};

    Detection::TaskSuiteIdToResults taskSuiteIdToResults{
        {
            TS_1,
            {
                {// Result from user 1
                    TS_1,
                    "unused",
                    ACCEPTED,
                    USER_1,
                    {Detection::TaskInput{SOURCE_1}},
                    {
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.10, 0.20),
                                    geolib3::Point2(0.15, 0.26)
                                ),
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.10, 0.20),
                                    geolib3::Point2(0.15, 0.26)
                                ),
                            })
                        }
                    }
                },

                {// Result from user 2
                    TS_1,
                    "unused",
                    ACCEPTED,
                    USER_2,
                    {Detection::TaskInput{SOURCE_1}},
                    {
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.5, 0.5),
                                    geolib3::Point2(0.6, 0.6)
                                )
                            })
                        }
                    }
                },
                {// Result from user 3
                    TS_1,
                    "unused",
                    ACCEPTED,
                    USER_3,
                    {Detection::TaskInput{SOURCE_1}},
                    {
                        Detection::TaskOutput{
                            DetectionResult::IsNotEmpty,
                            std::vector<geolib3::BoundingBox>({
                                geolib3::BoundingBox(
                                    geolib3::Point2(0.7, 0.7),
                                    geolib3::Point2(0.8, 0.8)
                                )
                            })
                        }
                    }
                }
            }
        }
    };

    auto tsResults = mergeTasksResults<Detection>(idToTaskSuite, taskSuiteIdToResults);

    // Validate results.
    // User 1 pointed the same area 2 times. It will not be counted
    EXPECT_TRUE(!tsResults.empty());
    const auto& suiteResult = tsResults.begin()->second;
    const auto& taskResults = suiteResult.taskResults;
    EXPECT_TRUE(!taskResults.empty());
    EXPECT_EQ(taskResults[0].input.imageUrl(), SOURCE_1);
    EXPECT_TRUE(taskResults[0].output.bboxes().empty());
}

} // test
} // toloka
} // mrc
} // maps
