#include "fixtures.h"

#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/id.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/object_manager/impl/store.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/rotation.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/unit_test/include/frame.h>

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/frame_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/recognition_gateway.h>

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

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

struct ObjectStoreFixture: public BaseFixture {
    ObjectStoreFixture() {
        auto txn = newTxn();

        devices = {
            {db::eye::MrcDeviceAttrs{"M1"}},
        };
        db::eye::DeviceGateway(*txn).insertx(devices);

        frames = {
            {devices[0].id(), identical, makeUrlContext(1, "1"), {1200, 800}, time()},
            {devices[0].id(), identical, makeUrlContext(2, "2"), {1200, 800}, time()},
            {devices[0].id(), identical, makeUrlContext(3, "3"), {1200, 800}, time()},
            {devices[0].id(), identical, makeUrlContext(4, "4"), {1200, 800}, time()},
            {devices[0].id(), identical, makeUrlContext(5, "5"), {1200, 800}, time()},
            {devices[0].id(), identical, makeUrlContext(6, "6"), {1200, 800}, time()},
        };
        db::eye::FrameGateway(*txn).insertx(frames);

        frameLocations = {
            {frames[0].id(), geolib3::Point2{0, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[1].id(), geolib3::Point2{1, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[2].id(), geolib3::Point2{0, 2}, toRotation(geolib3::Heading(90), identical)},
            {frames[3].id(), geolib3::Point2{0, 2}, toRotation(geolib3::Heading(90), identical)},
            {frames[4].id(), geolib3::Point2{0, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[5].id(), geolib3::Point2{0, 0}, toRotation(geolib3::Heading(90), identical)},
        };
        db::eye::FrameLocationGateway(*txn).insertx(frameLocations);

        groups = {
            {frames[0].id(), db::eye::DetectionType::HouseNumber},
            {frames[1].id(), db::eye::DetectionType::HouseNumber},
            {frames[2].id(), db::eye::DetectionType::HouseNumber},
            {frames[3].id(), db::eye::DetectionType::HouseNumber},
            {frames[4].id(), db::eye::DetectionType::HouseNumber},
            {frames[5].id(), db::eye::DetectionType::HouseNumber},
        };
        db::eye::DetectionGroupGateway(*txn).insertx(groups);

        detections = {
            {groups[0].id(), db::eye::DetectedHouseNumber{{0, 0, 10, 10}, 1.0, "12"}},
            {groups[1].id(), db::eye::DetectedHouseNumber{{0, 0, 20, 20}, 1.0, "12"}},
            {groups[2].id(), db::eye::DetectedHouseNumber{{0, 0, 10, 10}, 1.0, "12"}},
            {groups[3].id(), db::eye::DetectedHouseNumber{{0, 0, 70, 70}, 1.0, "12"}},
            {groups[4].id(), db::eye::DetectedHouseNumber{{0, 0, 10, 10}, 1.0, "12"}},
            {groups[5].id(), db::eye::DetectedHouseNumber{{0, 0, 60, 60}, 1.0, "12"}},
        };
        db::eye::DetectionGateway(*txn).insertx(detections);

        objects = {
            {detections[0].id(), db::eye::HouseNumberAttrs{"12"}},
            {detections[3].id(), db::eye::HouseNumberAttrs{"12"}},
            {detections[5].id(), db::eye::HouseNumberAttrs{"12"}},
        };
        objects[2].setDeleted(true);
        db::eye::ObjectGateway(*txn).insertx(objects);

        relations = {
            {detections[0].id(), detections[1].id()},
            {detections[0].id(), detections[2].id()},
            {detections[3].id(), detections[4].id()},
            {detections[5].id(), detections[4].id()},
        };
        relations[3].setDeleted(true);
        db::eye::PrimaryDetectionRelationGateway(*txn).insertx(relations);

        objectLocations = {
            {objects[0].id(), geolib3::Point2{20, 0}, toRotation(geolib3::Heading(90), identical)},
            {objects[1].id(), geolib3::Point2{0, 0}, toRotation(geolib3::Heading(90), identical)},
            {objects[2].id(), geolib3::Point2{0, 0}, toRotation(geolib3::Heading(200), identical)},
        };
        db::eye::ObjectLocationGateway(*txn).insertx(objectLocations);

        txn->commit();
    }
};

TEST_F(ObjectStoreFixture, iterate_objects)
{
    ObjectStore store;
    store.extendByObjects(*newTxn(), objects);

    db::TIdSet objectIds;
    for (const db::eye::Object& object : store.objects()) {
        objectIds.insert(object.id());
    }

    db::TIdSet expectedIds{
        objects[0].id(),
        objects[1].id(),
        objects[2].id(),
    };

    EXPECT_EQ(objectIds, expectedIds);
}

TEST_F(ObjectStoreFixture, iterate_relations)
{
    ObjectStore store;
    store.extendByObjects(*newTxn(), objects);

    db::TIdSet relationIds;
    for (const db::eye::PrimaryDetectionRelation& relation : store.relations()) {
        relationIds.insert(relation.id());
    }

    db::TIdSet expectedIds{
        relations[0].id(),
        relations[1].id(),
        relations[2].id(),
        relations[3].id(),
    };

    EXPECT_EQ(relationIds, expectedIds);
}

TEST_F(ObjectStoreFixture, iterate_locations)
{
    ObjectStore store;
    store.extendByObjects(*newTxn(), objects);

    db::TIdSet locationObjectIds;
    for (const db::eye::ObjectLocation& location : store.objectLocations()) {
        locationObjectIds.insert(location.objectId());
    }

    db::TIdSet expectedIds{
        objects[0].id(),
        objects[1].id(),
        objects[2].id(),
    };

    EXPECT_EQ(locationObjectIds, expectedIds);
}

TEST_F(ObjectStoreFixture, extend_by_objects)
{
    ObjectStore store;
    EXPECT_EQ(store.objectsCount(), 0u);

    store.extendByObjects(
        *newTxn(),
        {objects[0], objects[1]}
    );

    EXPECT_EQ(store.objectsCount(), 2u);

    db::TIdSet objectIds;
    for (const db::eye::Object& object : store.objects()) {
        objectIds.insert(object.id());
    }

    db::TIdSet expectedObjectIds{
        objects[0].id(),
        objects[1].id(),
    };

    EXPECT_EQ(objectIds, expectedObjectIds);

    db::TIdSet relationIds;
    for (const db::eye::PrimaryDetectionRelation& relation : store.relations()) {
        relationIds.insert(relation.id());
    }

    db::TIdSet expectedRelationIds{
        relations[0].id(),
        relations[1].id(),
        relations[2].id(),
    };

    EXPECT_EQ(relationIds, expectedRelationIds);
}

TEST_F(ObjectStoreFixture, extend_by_detection_ids)
{
    ObjectStore store;
    EXPECT_EQ(store.objectsCount(), 0u);

    store.extendByDetectionIds(
        *newTxn(),
        db::TIds{detections[0].id(), detections[5].id()}
    );

    EXPECT_EQ(store.objectsCount(), 2u);

    db::TIdSet objectIds;
    for (const db::eye::Object& object : store.objects()) {
        objectIds.insert(object.id());
    }

    db::TIdSet expectedObjectIds{
        objects[0].id(),
        objects[2].id(),
    };

    EXPECT_EQ(objectIds, expectedObjectIds);

    db::TIdSet relationIds;
    for (const db::eye::PrimaryDetectionRelation& relation : store.relations()) {
        relationIds.insert(relation.id());
    }

    db::TIdSet expectedRelationIds{
        relations[0].id(),
        relations[1].id(),
        relations[3].id(),
    };

    EXPECT_EQ(relationIds, expectedRelationIds);
}

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