#include <mapreduce/yt/tests/yt_unittest_lib/yt_unittest_lib.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/sync_detection/impl/sync.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/sync_detection/tests/common.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/sync_detection/tests/fixture.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/recognition_gateway.h>

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

TEST_F(Fixture, new_group)
{
    const db::TId frameId = frames[0].id();

    BatchItems items = {
        BatchItem {
            frames[0],
            {recognitions[0]},
            std::nullopt,
            {},
            {}
        }
    };

    auto txn = newTxn();
    sync(*txn, items);
    txn->commit();

    const auto [group, detections] = getGroup(frameId, db::eye::DetectionType::Sign);

    EXPECT_TRUE(group.txnId());
    EXPECT_TRUE(not group.approved());

    EXPECT_EQ(detections.size(), 1u);

    EXPECT_TRUE(
        hasDetectedSign(
            detections,
            traffic_signs::TrafficSign::ProhibitoryNoEntry,
            {100, 100, 200, 200}
        )
    );
}

TEST_F(Fixture, merge_group)
{
    const db::TId frameId = frames[3].id();

    const auto [oldGroup, _] = getGroup(frameId, db::eye::DetectionType::TrafficLight);

    BatchItems items = {
        BatchItem {
            frames[3],
            {recognitions[3], recognitions[4]},
            groups[0],
            {detections[0], detections[1]},
            {}
        }
    };

    auto txn = newTxn();
    sync(*txn, items);
    txn->commit();

    const auto [newGroup, newDetections] = getGroup(frameId, db::eye::DetectionType::TrafficLight);

    EXPECT_TRUE(newGroup.txnId() > oldGroup.txnId());
    EXPECT_TRUE(not newGroup.approved());

    EXPECT_EQ(newDetections.size(), 3u);

    EXPECT_TRUE(hasDetectedTrafficLight(newDetections, {100, 100, 120, 120}));
    EXPECT_TRUE(hasDetectedTrafficLight(newDetections, {170, 171, 200, 200}));
    EXPECT_TRUE(isDeleted(newDetections, detections[0].id()));
}

TEST_F(Fixture, delete_group)
{
    const db::TId frameId = frames[3].id();
    frames[3].setDeleted(true);

    const auto [oldGroup, _] = getGroup(frameId, db::eye::DetectionType::TrafficLight);

    BatchItems items = {
        BatchItem {
            frames[3],
            {recognitions[3], recognitions[4]},
            groups[0],
            {detections[0], detections[1]},
            {}
        }
    };

    auto txn = newTxn();
    sync(*txn, items);
    txn->commit();

    const auto [newGroup, newDetections] = getGroup(frameId, db::eye::DetectionType::TrafficLight);

    EXPECT_TRUE(newGroup.txnId() > oldGroup.txnId());
    EXPECT_TRUE(newGroup.approved());

    EXPECT_TRUE(
        std::all_of(
            newDetections.begin(), newDetections.end(),
            [&](const auto& detection) {
                return detection.deleted();
            }
        )
    );
}

TEST_F(Fixture, approve_group)
{
    const db::TId frameId = frames[3].id();

    const auto [oldGroup, _] = getGroup(frameId, db::eye::DetectionType::TrafficLight);
    EXPECT_FALSE(oldGroup.approved());

    auto txn = newTxn();
    recognitions[5].setValue(db::eye::DetectedTrafficLights{});
    db::eye::RecognitionGateway(*txn).updatex(recognitions[5]);

    BatchItems items = {
        BatchItem {
            frames[3],
            {recognitions[3], recognitions[4], recognitions[5]},
            groups[0],
            {detections[0], detections[1]},
            {}
        }
    };

    sync(*txn, items);
    txn->commit();

    const auto [newGroup, newDetections] = getGroup(frameId, db::eye::DetectionType::TrafficLight);

    EXPECT_TRUE(newGroup.txnId() > oldGroup.txnId());
    EXPECT_TRUE(newGroup.approved());
}

TEST_F(Fixture, delete_empty_group)
{
    const db::TId frameId = frames[0].id();
    frames[0].setDeleted(true);

    BatchItems items = {
        BatchItem {
            frames[0],
            {recognitions[0]},
            std::nullopt,
            {},
            {}
        }
    };

    auto txn = newTxn();
    sync(*txn, items);
    txn->commit();

    const auto [newGroup, newDetections] = getGroup(frameId, db::eye::DetectionType::Sign);

    EXPECT_TRUE(newGroup.txnId());
    EXPECT_TRUE(newGroup.approved());
    EXPECT_TRUE(newDetections.empty());
}

TEST_F(Fixture, no_changes)
{
    const db::TId frameId = frames[6].id();

    const auto [oldGroup, _] = getGroup(frameId, db::eye::DetectionType::TrafficLight);


    BatchItems items = {
        BatchItem {
            frames[6],
            {recognitions[7]},
            groups[1],
            {detections[2]},
            {}
        }
    };


    auto txn = newTxn();
    sync(*txn, items);
    txn->commit();

    const auto [newGroup, __] = getGroup(frameId, db::eye::DetectionType::TrafficLight);

    EXPECT_EQ(newGroup.txnId(), oldGroup.txnId());
}

TEST_F(Fixture, remove_empty_recognition)
{
    int16_t version = 10;
    db::eye::Recognition recognition {
        frames[6].id(),
        frames[6].orientation(),
        db::eye::RecognitionType::DetectTrafficLight,
        db::eye::RecognitionSource::Model,
        version,
        db::eye::DetectedTrafficLights {}
    };

    {
        auto txn = newTxn();
        db::eye::RecognitionGateway(*txn).insertx(recognition);
        txn->commit();
    }

    const db::TId frameId = frames[6].id();

    const auto [oldGroup, _] = getGroup(frameId, db::eye::DetectionType::TrafficLight);


    BatchItems items = {
        BatchItem {
            frames[6],
            {recognitions[7], recognition},
            groups[1],
            {detections[2]},
            {}
        }
    };


    auto txn = newTxn();
    sync(*txn, items);
    txn->commit();

    const auto [newGroup, __] = getGroup(frameId, db::eye::DetectionType::TrafficLight);

    EXPECT_EQ(newGroup.txnId() > oldGroup.txnId(), true);

    {
        auto txn = newTxn();
        db::eye::Detections detections = db::eye::DetectionGateway(*txn).load(
            db::eye::table::Detection::groupId == newGroup.id()
                && !db::eye::table::Detection::deleted
        );
        EXPECT_EQ(detections.empty(), true);
    }

    {
        auto txn = newTxn();
        db::eye::Recognitions recognitions = db::eye::RecognitionGateway(*txn).load(
            db::eye::table::Recognition::id == recognition.id()
        );
        EXPECT_EQ(recognitions.empty(), true);
    }
}

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