#include <maps/libs/http/include/test_utils.h>
#include <maps/libs/json/include/prettify.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/recognition_task/impl/house_number_handler.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/recognition_task/tests/fixture.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/recognition_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/recognition_value.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/toloka/task_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/toloka_manager/include/toloka_manager.h>

using namespace maps::mrc::db::eye;
using namespace maps::mrc::db::toloka;

namespace maps::mrc::eye::tests {

TEST_F(Fixture, process_toloka_recognition_fullstack_test)
{
    // prepare database
    Recognitions recognitions;

    {
        auto txn = txnHandle();

        recognitions = {
            {
                frames[1].id(),
                common::ImageOrientation(common::Rotation::CW_0),
                RecognitionType::DetectHouseNumber,
                RecognitionSource::Toloka,
                1
            },
        };
        RecognitionGateway(*txn).insertx(recognitions);

        txn->commit();
    }

    // test
    FrameUrlResolver urlResolver(
        "http://fake-browser-mrc.yandex.ru",
        "http://fake-browser-pro-mrc.yandex.ru");

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 1u);
        txn->commit();
    }

    {
        auto txn = txnHandle();

        RecognitionTasks recognitionTasks = RecognitionTaskGateway(*txn).load(
            db::eye::table::RecognitionTask::recognitionId == recognitions[0].id()
        );
        EXPECT_EQ(recognitionTasks.size(), 1u);
        RecognitionTask& recognitionTask = recognitionTasks[0];
        EXPECT_TRUE(!recognitionTask.parentId());
        EXPECT_TRUE(recognitionTask.txnId() != 0);
        db::toloka::Task task = db::toloka::TaskGateway(*txn).loadById(recognitionTask.taskId());
        EXPECT_EQ(task.type(), toloka::HouseNumberDetectionTask::DB_TASK_TYPE);
        auto input = toloka::parseJson<toloka::DetectionInput>(task.inputValues());
        EXPECT_EQ(
            input.imageUrl(),
            urlResolver.image(frames[1], recognitions[0].orientation(), db::FeaturePrivacy::Public)
        );
        EXPECT_TRUE(!task.outputValues());
        task.setOutputValues(R"(
            {
                "result": "is_not_empty",
                "polygons": [
                    {
                        "type": "rectangle",
                        "data": {
                            "p1": {"x": 0.0156250, "y": 0.027777778},
                            "p2": {"x": 0.0234375, "y": 0.041666667}
                        }
                    }
                ]
            }
        )");

        db::toloka::TaskGateway(*txn).updatex(task);

        txn->commit();
    }

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 1u);
        txn->commit();
    }

    {
        auto txn = txnHandle();

        RecognitionTasks recognitionTasks = RecognitionTaskGateway(*txn).load(
            db::eye::table::RecognitionTask::recognitionId == recognitions[0].id(),
            sql_chemistry::orderBy(db::eye::table::RecognitionTask::id).asc()
        );
        EXPECT_EQ(recognitionTasks.size(), 2u);
        EXPECT_TRUE(recognitionTasks[0].txnId() != 0);
        EXPECT_TRUE(recognitionTasks[1].txnId() != 0);
        EXPECT_EQ(recognitionTasks[1].parentId().value(), recognitionTasks[0].id());
        db::toloka::Task task = db::toloka::TaskGateway(*txn).loadById(recognitionTasks[1].taskId());
        EXPECT_EQ(task.type(), toloka::HouseNumberRecognitionTask::DB_TASK_TYPE);
        task.setOutputValues(R"({"state": "ok", "house_number": "33"})");
        db::toloka::TaskGateway(*txn).updatex(task);

        txn->commit();
    }

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 1u);
        txn->commit();
    }

    {
        auto txn = txnHandle();

        Recognition recognition = RecognitionGateway(*txn).loadById(recognitions[0].id());
        EXPECT_TRUE(recognition.txnId() > recognitions[0].txnId());
        auto expectedJson = json::Value::fromString(R"([{
            "box": [[20, 20], [30, 30]],
            "cnf": 1.0,
            "number": "33"
        }])");
        EXPECT_EQ(expectedJson, toJson(recognition.value<DetectedHouseNumbers>()));

        txn->commit();
    }
}

TEST_F(Fixture, process_toloka_recognition_image_not_loaded)
{
    // prepare database
    Recognitions recognitions;
    Tasks tasks;
    RecognitionTasks recognitionTasks;

    {
        auto txn = txnHandle();

        recognitions = {
            {
                frames[2].id(),
                common::ImageOrientation(common::Rotation::CW_0),
                RecognitionType::DetectHouseNumber,
                RecognitionSource::Toloka,
                1
            },
        };
        RecognitionGateway(*txn).insertx(recognitions);

        tasks = {
            {
                Task(db::toloka::Platform::Toloka)
                      .setType(TaskType::HouseNumberDetection)
                      .setStatus(TaskStatus::Finished)
                      .setInputValues(R"({"source": "https://yandex.ru/image-1"})")
                      .setOutputValues(R"(
                          {
                              "result": "is_not_empty",
                              "polygons": [
                                  {
                                      "type": "rectangle",
                                      "data": {
                                          "p1": {"x": 0.0156250, "y": 0.027777778},
                                          "p2": {"x": 0.0234375, "y": 0.041666667}
                                      }
                                  }
                              ]
                          }
                      )")
            },
            {
                Task(db::toloka::Platform::Toloka)
                      .setType(TaskType::HouseNumberRecognition)
                      .setStatus(TaskStatus::Finished)
                      .setInputValues(R"({
                          "image": "https://yandex.ru/image-1",
                          "bbox": [[30, 30], [40, 40]]
                      })")
                      .setOutputValues(R"({"state": "not-loaded"})")
            },
        };
        TaskGateway(*txn).insertx(tasks);

        recognitionTasks = {
            {
                recognitions[0].id(),
                tasks[0].id()
            },
            {
                recognitions[0].id(),
                tasks[1].id()
            },
        };
        RecognitionTaskGateway(*txn).insertx(recognitionTasks);

        txn->commit();
    }
    // test
    FrameUrlResolver urlResolver(
        "http://fake-browser-mrc.yandex.ru",
        "http://fake-browser-pro-mrc.yandex.ru");

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 1u);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        Recognition recognition = RecognitionGateway(*txn).loadById(recognitions[0].id());
        EXPECT_TRUE(recognition.txnId() > recognitions[0].txnId());
        EXPECT_EQ(recognition.value<DetectedHouseNumbers>().size(), 0u);
        txn->commit();
    }
}

TEST_F(Fixture, process_toloka_recognition_empty_detection)
{
    // prepare database
    Recognitions recognitions;
    Tasks tasks;
    RecognitionTasks recognitionTasks;

    {
        auto txn = txnHandle();

        // prepare database
        recognitions = {
            {
                frames[2].id(),
                common::ImageOrientation(common::Rotation::CW_0),
                RecognitionType::DetectHouseNumber,
                RecognitionSource::Toloka,
                2
            },
        };
        RecognitionGateway(*txn).insertx(recognitions);

        tasks = {
            {
                Task(db::toloka::Platform::Toloka)
                      .setType(TaskType::HouseNumberDetection)
                      .setStatus(TaskStatus::Finished)
                      .setInputValues(R"({"image": "https://yandex.ru/image-1"})")
                      .setOutputValues(R"({"result": "is_empty", "polygons": []})")
            },
        };
        TaskGateway(*txn).insertx(tasks);

        recognitionTasks = {
            {
                recognitions[0].id(),
                tasks[0].id()
            },
        };
        RecognitionTaskGateway(*txn).insertx(recognitionTasks);

        txn->commit();
    }

    // test
    FrameUrlResolver urlResolver(
        "http://fake-browser-mrc.yandex.ru",
        "http://fake-browser-pro-mrc.yandex.ru");

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 1u);
        txn->commit();
    }

    {
        auto txn = txnHandle();

        Recognition recognition = RecognitionGateway(*txn).loadById(recognitions[0].id());
        EXPECT_TRUE(recognition.txnId() > recognitions[0].txnId());
        EXPECT_EQ(recognition.value<DetectedHouseNumbers>().size(), 0u);

        txn->commit();
    }
}

TEST_F(Fixture, process_toloka_recognition_hn_recognitions_by_two_steps)
{
    // prepare database
    Recognitions recognitions;
    Tasks tasks;
    RecognitionTasks recognitionTasks;

    {
        auto txn = txnHandle();

        recognitions = {
            {
                frames[2].id(),
                common::ImageOrientation(common::Rotation::CW_0),
                RecognitionType::DetectHouseNumber,
                RecognitionSource::Toloka,
                4
            },
        };
        RecognitionGateway(*txn).insertx(recognitions);

        tasks = {
            {
                Task(db::toloka::Platform::Toloka)
                      .setType(TaskType::HouseNumberDetection)
                      .setStatus(TaskStatus::Finished)
                      .setInputValues(R"({"image": "https://yandex.ru/image-1"})")
                      .setOutputValues(R"(
                          {
                              "result": "is_not_empty",
                              "polygons": [
                                  {
                                      "type": "rectangle",
                                      "data": {
                                          "p1": {"x": 0.00078125, "y": 0.002777778},
                                          "p2": {"x": 0.00234375, "y": 0.005555556}
                                      }
                                  },
                                  {
                                      "type": "rectangle",
                                      "data": {
                                          "p1": {"x": 0.00390625, "y": 0.008333333},
                                          "p2": {"x": 0.00546875, "y": 0.011111111}
                                      }
                                  }
                              ]
                          }
                      )")
            },
        };
        TaskGateway(*txn).insertx(tasks);

        recognitionTasks = {
            {
                recognitions[0].id(),
                tasks[0].id()
            },
        };
        RecognitionTaskGateway(*txn).insertx(recognitionTasks);

        txn->commit();
    }

    // test
    FrameUrlResolver urlResolver(
        "http://fake-browser-mrc.yandex.ru",
        "http://fake-browser-pro-mrc.yandex.ru");

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 2u);
        txn->commit();
    }

    {
        auto txn = txnHandle();

        RecognitionTasks recognitionTasks = RecognitionTaskGateway(*txn).load(
            db::eye::table::RecognitionTask::recognitionId == recognitions[0].id(),
            sql_chemistry::orderBy(db::eye::table::RecognitionTask::id).asc()
        );
        EXPECT_EQ(recognitionTasks.size(), 3u);
        EXPECT_TRUE(!recognitionTasks[0].parentId());
        EXPECT_TRUE(recognitionTasks[0].txnId() != 0);
        EXPECT_TRUE(recognitionTasks[1].txnId() != 0);
        EXPECT_TRUE(recognitionTasks[2].txnId() != 0);
        EXPECT_EQ(recognitionTasks[1].parentId().value(), recognitionTasks[0].id());
        EXPECT_EQ(recognitionTasks[2].parentId().value(), recognitionTasks[0].id());
        db::toloka::Task task1 = db::toloka::TaskGateway(*txn).loadById(recognitionTasks[1].taskId());
        db::toloka::Task task2 = db::toloka::TaskGateway(*txn).loadById(recognitionTasks[2].taskId());
        EXPECT_EQ(task1.type(), toloka::HouseNumberRecognitionTask::DB_TASK_TYPE);
        EXPECT_TRUE(!task1.outputValues());
        EXPECT_EQ(task2.type(), toloka::HouseNumberRecognitionTask::DB_TASK_TYPE);
        EXPECT_TRUE(!task2.outputValues());
        auto input1 = toloka::parseJson<toloka::HouseNumberRecognitionInput>(task1.inputValues());
        auto input2 = toloka::parseJson<toloka::HouseNumberRecognitionInput>(task2.inputValues());
        EXPECT_EQ(
            input1.imageUrl(),
            urlResolver.image(frames[2], recognitions[0].orientation(), db::FeaturePrivacy::Public)
        );
        EXPECT_EQ(
            input2.imageUrl(),
            urlResolver.image(frames[2], recognitions[0].orientation(), db::FeaturePrivacy::Public)
        );
        EXPECT_TRUE(
            input1.bbox() == common::ImageBox(1, 2, 3, 4) && input2.bbox() == common::ImageBox(5, 6, 7, 8)
            ||
            input1.bbox() == common::ImageBox(5, 6, 7, 8) && input2.bbox() == common::ImageBox(1, 2, 3, 4)
        );

        auto& toUpdateTask = input1.bbox() == common::ImageBox(1, 2, 3, 4) ? task1 : task2;
        toUpdateTask.setOutputValues(R"({"state": "ok", "house_number": "33"})");
        db::toloka::TaskGateway(*txn).updatex(toUpdateTask);

        txn->commit();
    }

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 0u);
        txn->commit();
    }

    {
        auto txn = txnHandle();

        RecognitionTasks recognitionTasks = RecognitionTaskGateway(*txn).load(
            db::eye::table::RecognitionTask::recognitionId == recognitions[0].id(),
            sql_chemistry::orderBy(db::eye::table::RecognitionTask::id).asc()
        );
        EXPECT_EQ(recognitionTasks.size(), 3u);
        EXPECT_TRUE(!recognitionTasks[0].parentId());
        EXPECT_TRUE(recognitionTasks[0].txnId() != 0);
        EXPECT_TRUE(recognitionTasks[1].txnId() != 0);
        EXPECT_TRUE(recognitionTasks[2].txnId() != 0);
        EXPECT_EQ(recognitionTasks[1].parentId().value(), recognitionTasks[0].id());
        EXPECT_EQ(recognitionTasks[2].parentId().value(), recognitionTasks[0].id());
        db::toloka::Task task1 = db::toloka::TaskGateway(*txn).loadById(recognitionTasks[1].taskId());
        db::toloka::Task task2 = db::toloka::TaskGateway(*txn).loadById(recognitionTasks[2].taskId());
        EXPECT_EQ(task1.type(), toloka::HouseNumberRecognitionTask::DB_TASK_TYPE);
        EXPECT_EQ(task2.type(), toloka::HouseNumberRecognitionTask::DB_TASK_TYPE);
        EXPECT_TRUE(
            task1.outputValues() && !task2.outputValues()
            ||
            !task2.outputValues() && task2.outputValues()
        );
        const db::toloka::Task& completedTask = task1.outputValues() ? task1 : task2;
        auto output = toloka::parseJson<toloka::HouseNumberRecognitionOutput>(*completedTask.outputValues());
        EXPECT_EQ(output.state(), toloka::HouseNumberRecognitionState::Ok);
        EXPECT_EQ(output.houseNumber(), "33");

        db::toloka::Task& toUpdateTask = task1.outputValues() ? task2 : task1;
        toUpdateTask.setOutputValues(R"({"state": "ok", "house_number": "22"})");
        db::toloka::TaskGateway(*txn).updatex(toUpdateTask);

        txn->commit();
    }

    {
        auto txn = txnHandle();
        EXPECT_EQ(handleHouseNumberRecognitions(*txn, urlResolver, recognitions), 1u);
        txn->commit();
    }

    {
        auto txn = txnHandle();

        Recognition recognition = RecognitionGateway(*txn).loadById(recognitions[0].id());
        EXPECT_TRUE(recognition.txnId() > recognitions[0].txnId());
        EXPECT_TRUE(recognition.hasValue());
        std::vector<json::Value> expectedJsons{
            json::Value::fromString(R"({"number": "22", "box": [[5, 6], [7, 8]], "cnf": 1.0})"),
            json::Value::fromString(R"({"number": "33", "box": [[1, 2], [3, 4]], "cnf": 1.0})")
        };
        DetectedHouseNumbers value = recognition.value<DetectedHouseNumbers>();
        std::vector<json::Value> resultJsons;
        for (size_t i = 0; i < value.size(); i++) {
            resultJsons.push_back(toJson(value[i]));
        }

        EXPECT_TRUE(
            std::is_permutation(expectedJsons.begin(), expectedJsons.end(), resultJsons.begin())
        );

        txn->commit();
    }
}

} // namespace maps::mrc::eye::tests
