#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/id.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/object_manager/impl/greedy_merge.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 "fixtures.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 {

std::function<bool(const db::TIdSet&, const db::TIdSet&)>
makeAreAllowedToMergeFunc(const DetectionStore& store)
{
    return [&](const db::TIdSet& detectionIds1, const db::TIdSet& detectionIds2)
        {
            return !hasDifferentDetectionsOnSameFrame(store, detectionIds1, detectionIds2);
        };
}


struct GreedyMergeFixture: public BaseFixture {
    GreedyMergeFixture() {
        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(3, "3"), {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)},
        };
        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},
        };
        db::eye::DetectionGroupGateway(*txn).insertx(groups);

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

        txn->commit();
    }
};

TEST_F(GreedyMergeFixture, has_different_detections_on_same_frame_with_detections_on_same_frame)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);

    EXPECT_TRUE(
        hasDifferentDetectionsOnSameFrame(
            store,
            db::TIdSet{detections[0].id(), detections[2].id()},
            db::TIdSet{detections[1].id(), detections[3].id()}
        )
    );
}

TEST_F(GreedyMergeFixture, has_different_detections_on_same_frame_with_same_detections)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);

    EXPECT_FALSE(
        hasDifferentDetectionsOnSameFrame(
            store,
            db::TIdSet{detections[0].id(), detections[2].id()},
            db::TIdSet{detections[0].id(), detections[3].id()}
        )
    );
}

TEST_F(GreedyMergeFixture, has_different_detections_on_same_frame_without_detections_on_same_frame)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);

    EXPECT_FALSE(
        hasDifferentDetectionsOnSameFrame(
            store,
            db::TIdSet{detections[1].id(), detections[2].id()},
            db::TIdSet{detections[3].id()}
        )
    );
}

TEST_F(GreedyMergeFixture, greedy_merging_empty)
{
    DetectionStore store;
    const std::vector<ObjectsInPassage> objectsByPassages{};
    const std::vector<MatchedObjects> objectsMatches{};

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{};

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_merge_objects)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[3].id(),
                    {detections[3].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        detections[3].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[3].id(), /* passageIndx = */ 1},
            .relevance = 1.
        }
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[1].id(),
            detections[2].id(),
            detections[3].id(),
        }
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_do_not_merge_objects_with_common_frames)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[0].id(),
                    {detections[0].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        detections[0].id(),
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                        detections[3].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[0].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[1].id(), /* passageIndx = */ 1},
            .relevance = 1.
        }
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[0].id(),
            detections[2].id(),
        },
        {
            detections[1].id(),
            detections[3].id(),
        }
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_complex_1)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[2].id(),
                    {detections[2].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[3].id(),
                    {detections[3].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        detections[3].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[2].id(), /* passageIndx = */ 1},
            .relevance = 2.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[2].id(), /* passageIndx = */ 1},
            .objectPassageIndx2 = {detections[3].id(), /* passageIndx = */ 2},
            .relevance = 1.
        },
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[1].id(),
            detections[2].id(),
            detections[3].id(),
        },
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_complex_2)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[2].id(),
                    {detections[2].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[0].id(),
                    {detections[0].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        detections[0].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[2].id(), /* passageIndx = */ 1},
            .relevance = 2.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[2].id(), /* passageIndx = */ 1},
            .objectPassageIndx2 = {detections[0].id(), /* passageIndx = */ 2},
            .relevance = 1.
        },
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[1].id(),
            detections[2].id(),
        },
        {
            detections[0].id(),
        },
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_complex_3)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[2].id(),
                    {detections[2].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[0].id(),
                    {detections[0].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        detections[0].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[2].id(), /* passageIndx = */ 1},
            .relevance = 2.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[0].id(), /* passageIndx = */ 2},
            .objectPassageIndx2 = {detections[2].id(), /* passageIndx = */ 1},
            .relevance = 1.
        },
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[1].id(),
            detections[2].id(),
        },
        {
            detections[0].id(),
        },
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_complex_4)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[0].id(),
                    {detections[0].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        detections[0].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[2].id(),
                    {detections[2].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[3].id(),
                    {detections[3].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        detections[3].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[0].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[2].id(), /* passageIndx = */ 2},
            .relevance = 2.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 1},
            .objectPassageIndx2 = {detections[3].id(), /* passageIndx = */ 3},
            .relevance = 1.
        },
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[0].id(),
            detections[2].id(),
        },
        {
            detections[1].id(),
            detections[3].id(),
        },
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_complex_5)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[0].id(),
                    {detections[0].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[0].id(),
                    {
                        detections[0].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[2].id(),
                    {detections[2].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[3].id(),
                    {detections[3].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        detections[3].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[0].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[2].id(), /* passageIndx = */ 2},
            .relevance = 2.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 1},
            .objectPassageIndx2 = {detections[3].id(), /* passageIndx = */ 3},
            .relevance = 1.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[0].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[3].id(), /* passageIndx = */ 3},
            .relevance = 0.5
        },
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[0].id(),
            detections[2].id(),
        },
        {
            detections[1].id(),
            detections[3].id(),
        },
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

TEST_F(GreedyMergeFixture, greedy_merging_complex_6)
{
    DetectionStore store;
    store.extendByDetections(*newTxn(), detections);
    const std::vector<ObjectsInPassage> objectsByPassages{
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[1].id(),
                    {detections[1].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[1].id(),
                    {
                        detections[1].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[2].id(),
                    {detections[2].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[2].id(),
                    {
                        detections[2].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[3].id(),
                    {detections[3].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[3].id(),
                    {
                        detections[3].id(),
                    }
                }
            }
        },
        ObjectsInPassage{
            .objectByPrimaryId = {
                {
                    detections[4].id(),
                    {detections[4].id(), db::eye::HouseNumberAttrs{"12"}}
                }
            },
            .locationByPrimaryId = {
                {
                    detections[4].id(),
                    {
                        geolib3::Point2{20, 0},
                        toRotation(geolib3::Heading(90), identical)
                    }
                }
            },
            .detectionIdsByPrimaryId = {
                {
                    detections[4].id(),
                    {
                        detections[4].id(),
                    }
                }
            }
        }
    };
    const std::vector<MatchedObjects> objectsMatches{
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[2].id(), /* passageIndx = */ 1},
            .relevance = 2.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[3].id(), /* passageIndx = */ 2},
            .objectPassageIndx2 = {detections[4].id(), /* passageIndx = */ 3},
            .relevance = 1.
        },
        MatchedObjects{
            .objectPassageIndx1 = {detections[1].id(), /* passageIndx = */ 0},
            .objectPassageIndx2 = {detections[3].id(), /* passageIndx = */ 2},
            .relevance = 0.5
        },
    };

    const std::vector<db::TIdSet> result
        = greedyObjectsMerging(objectsMatches, objectsByPassages, makeAreAllowedToMergeFunc(store));

    const std::vector<db::TIdSet> expected{
        {
            detections[1].id(),
            detections[2].id(),
            detections[3].id(),
            detections[4].id(),
        },
    };

    EXPECT_THAT(result, ::testing::UnorderedElementsAreArray(expected));
}

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