#include <maps/wikimap/mapspro/services/mrc/eye/lib/detection/tests/fixture.h>

#include <maps/wikimap/mapspro/services/mrc/eye/lib/detection/include/superpoints_match.h>

#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/id.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/gmock_in_unittest/gmock.h>
#include <library/cpp/testing/unittest/registar.h>
#include <mapreduce/yt/tests/yt_unittest_lib/yt_unittest_lib.h>

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

struct SuperpointsFixture: public Fixture {
    SuperpointsFixture();

    db::eye::Devices devices;
    db::eye::Frames frames;
    db::eye::FrameLocations locations;
    db::eye::FramePrivacies privacies;
    db::eye::DetectionGroups groups;
    db::eye::Detections detections;

    DetectionStore store;

};

SuperpointsFixture::SuperpointsFixture()
{
    devices = insertx<db::eye::DeviceGateway>(
        db::eye::Devices {
            {db::eye::MrcDeviceAttrs{""}}
        }
    );

    frames = insertx<db::eye::FrameGateway>(
        db::eye::Frames {
            {devices[0].id(), identical, makeUrlContext(1, "1s"), {384, 216}, time()},
            {devices[0].id(), identical, makeUrlContext(2, "2s"), {384, 216}, time()},
            {devices[0].id(), identical, makeUrlContext(3, "3s"), {2601, 1463}, time()},
            {devices[0].id(), identical, makeUrlContext(4, "4s"), {1920, 1080}, time()},
        }
    );

    locations = insertx<db::eye::FrameLocationGateway>(
        db::eye::FrameLocations {
            {frames[0].id(), geolib3::Point2{4896473.70, 7584560.40}, toRotation(geolib3::Heading(90), identical)},
            {frames[1].id(), geolib3::Point2{4896473.70, 7584560.41}, toRotation(geolib3::Heading(90), identical)},
            {frames[2].id(), geolib3::Point2{4896473.70, 7584560.41}, toRotation(geolib3::Heading(90), identical)},
            {frames[3].id(), geolib3::Point2{4896473.70, 7584560.41}, toRotation(geolib3::Heading(90), identical)},
        }
    );
    for (const auto& frame : frames) {
        privacies.emplace_back(frame.id(), db::FeaturePrivacy::Public);
    }
    privacies = insertx<db::eye::FramePrivacyGateway>(std::move(privacies));

    groups = insertx<db::eye::DetectionGroupGateway>(
        db::eye::DetectionGroups {
            {frames[0].id(), db::eye::DetectionType::Sign},
            {frames[1].id(), db::eye::DetectionType::Sign},
            {frames[2].id(), db::eye::DetectionType::Sign},
            {frames[3].id(), db::eye::DetectionType::Sign},
        }
    );

    detections = insertx<db::eye::DetectionGateway>(
        db::eye::Detections {
            {groups[0].id(), db::eye::DetectedSign{{315, 80, 323, 94}, traffic_signs::TrafficSign::ProhibitoryNoParkingOrStopping, 1.0, false, 0.0}},
            {groups[1].id(), db::eye::DetectedSign{{315, 80, 323, 94}, traffic_signs::TrafficSign::ProhibitoryNoParkingOrStopping, 1.0, false, 0.0}},
        }
    );

    store = DetectionStore{
        byId(groups),
        byId(detections),
        byId(frames),
        byId(locations),
        byId(privacies),
        byId(devices)
    };
};

int matrixRank(const cv::Mat& mat) {
    constexpr double EPSILON = 1e-5;

    cv::Mat d, u, vt;
    cv::SVD::compute(mat, d, u, vt);
    int rank = 0;
    for (int row = 0; row < d.rows; row++) {
        if (d.at<double>(row) > EPSILON) {
            rank++;
        }
    }
    return rank;
}

Y_UNIT_TEST_SUITE_F(superpoints_frame_matching, SuperpointsFixture)
{

Y_UNIT_TEST(empty)
{
    const FrameLoader loader = FrameLoader::fromConfig(config());
    const NYT::IClientBasePtr client = NYT::NTesting::CreateTestClient();

    FrameSuperpointsMatcher matcher(client, loader, false);
    MatchedFramesPairs matchedFP = matcher.makeMatches(store, {});
    EXPECT_EQ(matchedFP.size(), 0u);
}

Y_UNIT_TEST(simple_match)
{
    const FrameLoader loader = FrameLoader::fromConfig(config());
    const NYT::IClientBasePtr client = NYT::NTesting::CreateTestClient();

    FrameSuperpointsMatcher matcher(client, loader, false);
    MatchedFramesPairs matchedFP = matcher.makeMatches(store, {{frames[0].id(), frames[1].id()}});
    EXPECT_EQ(matchedFP.size(), 1u);
    EXPECT_EQ(matrixRank(matchedFP[0].match.fundMatrix), 2);
    EXPECT_GE(matchedFP[0].match.goodPtsCnt, 15);
    EXPECT_GE(matchedFP[0].match.hull0.size(), 0u);
    EXPECT_GE(matchedFP[0].match.hull1.size(), 0u);
}

Y_UNIT_TEST(match_large_frame)
{
    const FrameLoader loader = FrameLoader::fromConfig(config());
    const NYT::IClientBasePtr client = NYT::NTesting::CreateTestClient();

    FrameSuperpointsMatcher matcher(client, loader, false);
    MatchedFramesPairs matchedFP = matcher.makeMatches(store, {{frames[2].id(), frames[3].id()}});
    EXPECT_EQ(matchedFP.size(), 1u);
    EXPECT_EQ(matrixRank(matchedFP[0].match.fundMatrix), 2);
    EXPECT_GE(matchedFP[0].match.goodPtsCnt, 15);
    EXPECT_GE(matchedFP[0].match.hull0.size(), 0u);
    EXPECT_GE(matchedFP[0].match.hull1.size(), 0u);
}

} // Y_UNIT_TEST_SUITE

Y_UNIT_TEST_SUITE_F(superpoints_detection_matching, SuperpointsFixture)
{

Y_UNIT_TEST(empty)
{
    const FrameLoader loader = FrameLoader::fromConfig(config());
    const NYT::IClientBasePtr client = NYT::NTesting::CreateTestClient();

    FrameSuperpointsMatcher frameMatcher(client, loader, false);
    DetectionSuperpointsMatcher matcher;
    MatchedFrameDetections matchedFD = matcher.makeMatches(store, {}, &frameMatcher);
    EXPECT_EQ(matchedFD.size(), 0u);
}

Y_UNIT_TEST(simple_match)
{
    const FrameLoader loader = FrameLoader::fromConfig(config());
    const NYT::IClientBasePtr client = NYT::NTesting::CreateTestClient();

    FrameSuperpointsMatcher frameMatcher(client, loader, false);
    DetectionSuperpointsMatcher matcher;
    MatchedFrameDetections matchedFD = matcher.makeMatches(store, {{detections[0].id(), detections[1].id()}}, &frameMatcher);
    EXPECT_EQ(matchedFD.size(), 1u);
}

} // Y_UNIT_TEST_SUITE

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