#include "eye/verification_source.h"
#include "eye/verified_detection_pair_match.h"
#include "fixtures.h"

#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/location.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/object_manager/impl/batch.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/object_manager/impl/metadata.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/object_manager/include/object_manager.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 <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/verified_detection_pair_match_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/verified_detection_missing_on_frame_gateway.h>

#include <maps/libs/sql_chemistry/include/system_information.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 BatchFixture : public BaseFixture {
    BatchFixture() {
        auto txn = newTxn();

        devices = {
            {db::eye::MrcDeviceAttrs{"M1"}},
        };

        db::eye::DeviceGateway(*txn).insertx(devices);

        const db::TId deviceId = devices[0].id();

        frames = {
            {deviceId, identical, makeUrlContext(1, "1"), {1600, 900}, time()},
            {deviceId, identical, makeUrlContext(2, "2"), {1600, 900}, time()},
            {deviceId, identical, makeUrlContext(3, "3"), {1600, 900}, time()},
            {deviceId, identical, makeUrlContext(4, "4"), {1600, 900}, time()},
            {deviceId, identical, makeUrlContext(5, "5"), {1600, 900}, time()},
            {deviceId, identical, makeUrlContext(6, "6"), {1600, 900}, time()},
        };

        db::eye::FrameGateway(*txn).insertx(frames);

        frameLocations = {
            {frames[0].id(), {0, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[1].id(), {0, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[2].id(), {0, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[3].id(), {0, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[4].id(), {0, 0}, toRotation(geolib3::Heading(90), identical)},
            {frames[5].id(), {0, 0}, toRotation(geolib3::Heading(90), identical)},
        };

        db::eye::FrameLocationGateway(*txn).insertx(frameLocations);

        groups = {
            {frames[0].id(), db::eye::DetectionType::TrafficLight},
            {frames[1].id(), db::eye::DetectionType::TrafficLight},
            {frames[2].id(), db::eye::DetectionType::TrafficLight},
            {frames[3].id(), db::eye::DetectionType::TrafficLight},
            {frames[4].id(), db::eye::DetectionType::TrafficLight},
            {frames[5].id(), db::eye::DetectionType::TrafficLight},
        };

        db::eye::DetectionGroupGateway(*txn).insertx(groups);

        txn->commit();
    }
};

TEST_F(BatchFixture, object_manager_load_batch_base)
{
    db::eye::Detections detections;
    db::eye::DetectionTypes detectionTypes{
        db::eye::DetectionType::TrafficLight
    };
    db::TId beginTxnId = 0;


    size_t limit1 = 3;
    auto batch1 = getNewBatch(*newTxn(), beginTxnId, limit1, detectionTypes);

    EXPECT_EQ(batch1.beginTxnId, 0);
    EXPECT_EQ(batch1.endTxnId, frameLocations[5].txnId() + 1);
    EXPECT_EQ(extractDetectionIds(batch1.detectionGroupIds).size(), 0u);
    EXPECT_THAT(extractDetectionGroupIds(batch1.detectionGroupIds),
        ::testing::UnorderedElementsAre(
            groups[0].id(),
            groups[1].id(),
            groups[2].id(),
            groups[3].id(),
            groups[4].id(),
            groups[5].id()
        ));

    {
        auto txn = newTxn();

        db::eye::Detections tmpDetections{
            {groups[5].id(), db::eye::DetectedTrafficLight{{100, 100, 300, 300}, 1.0}},
            {groups[4].id(), db::eye::DetectedTrafficLight{{100, 100, 240, 240}, 1.0}},
            {groups[3].id(), db::eye::DetectedTrafficLight{{100, 100, 200, 200}, 1.0}},
        };

        db::eye::DetectionGateway(*txn).insertx(tmpDetections);
        db::eye::DetectionGroupGateway(*txn).updatex(groups[3]);
        db::eye::DetectionGroupGateway(*txn).updatex(groups[4]);
        db::eye::DetectionGroupGateway(*txn).updatex(groups[5]);
        txn->commit();

        detections.insert(detections.end(), tmpDetections.begin(), tmpDetections.end());
    }

    size_t limit2 = 2;
    auto batch2 = getNewBatch(*newTxn(), batch1.endTxnId, limit2, detectionTypes);

    EXPECT_EQ(batch2.beginTxnId, batch1.endTxnId);
    EXPECT_EQ(batch2.endTxnId, detections.back().txnId() + 1);
    EXPECT_THAT(
        extractDetectionIds(batch2.detectionGroupIds),
        ::testing::UnorderedElementsAreArray(
            db::TIds{
                detections[0].id(),
                detections[1].id(),
                detections[2].id(),
            }
        )
    );

    size_t limit3 = 2;
    auto batch3 = getNewBatch(*newTxn(), batch2.endTxnId, limit3, detectionTypes);

    EXPECT_EQ(batch3.beginTxnId, batch2.endTxnId);
    EXPECT_EQ(batch3.endTxnId, batch2.endTxnId);
    EXPECT_EQ(extractDetectionIds(batch3.detectionGroupIds).size(), 0u);

    {
        auto txn = newTxn();

        frameLocations[4].setMercatorPos(geolib3::Point2(10, 10));

        db::eye::FrameLocationGateway(*txn).upsertx(frameLocations[4]);
        txn->commit();
    }

    size_t limit4 = 2;
    auto batch4 = getNewBatch(*newTxn(), batch3.endTxnId, limit4, detectionTypes);

    EXPECT_EQ(batch4.beginTxnId, batch3.endTxnId);
    EXPECT_EQ(batch4.endTxnId, frameLocations[4].txnId() + 1);
    EXPECT_THAT(
        extractDetectionIds(batch4.detectionGroupIds),
        ::testing::UnorderedElementsAreArray(
            db::TIds{
                detections[1].id(),
            }
        )
    );

    {
        auto txn = newTxn();

        frameLocations[3].setMercatorPos(geolib3::Point2(10, 10));

        db::eye::FrameLocationGateway(*txn).upsertx(frameLocations[3]);

        txn->commit();
    }
    {
        auto txn = newTxn();

        db::eye::Detections tmpDetections{
            {groups[2].id(), db::eye::DetectedTrafficLight{{100, 100, 300, 300}, 1.0}},
        };

        db::eye::DetectionGateway(*txn).insertx(tmpDetections);
        db::eye::DetectionGroupGateway(*txn).updatex(groups[2]);
        txn->commit();

        detections.insert(detections.end(), tmpDetections.begin(), tmpDetections.end());
    }

    size_t limit5 = 2;
    auto batch5 = getNewBatch(*newTxn(), batch4.endTxnId, limit5, detectionTypes);

    EXPECT_EQ(batch5.beginTxnId, batch4.endTxnId);
    EXPECT_EQ(batch5.endTxnId, groups[2].txnId() + 1);
    EXPECT_THAT(
        extractDetectionIds(batch5.detectionGroupIds),
        ::testing::UnorderedElementsAreArray(
            db::TIds{
                detections[2].id(),
                detections[3].id(),
            }
        )
    );

    size_t limit6 = 2;
    auto batch6 = getNewBatch(*newTxn(), batch5.endTxnId, limit6, detectionTypes);

    EXPECT_EQ(batch6.beginTxnId, batch5.endTxnId);
    EXPECT_EQ(batch6.endTxnId, batch5.endTxnId);
    EXPECT_EQ(extractDetectionIds(batch6.detectionGroupIds).size(), 0u);

    db::eye::VerifiedDetectionPairMatch match{
            db::eye::VerificationSource::Toloka,
            detections[0].id(),
            detections[1].id()
        };

    {
        auto txn = newTxn();

        db::eye::VerifiedDetectionPairMatchGateway{*txn}.insertx(match);
        txn->commit();
    }

    auto batch7 = getNewBatch(*newTxn(), batch6.endTxnId, 3, detectionTypes);

    EXPECT_EQ(batch7.beginTxnId, batch5.endTxnId);
    EXPECT_EQ(batch7.endTxnId, batch5.endTxnId);
    EXPECT_EQ(extractDetectionIds(batch7.detectionGroupIds).size(), 0u);

    {
        auto txn = newTxn();
        match.setApproved(true);
        db::eye::VerifiedDetectionPairMatchGateway{*txn}.updatex(match);
        txn->commit();
    }

    auto batch8 = getNewBatch(*newTxn(), batch7.endTxnId, 3, detectionTypes);

    EXPECT_EQ(batch8.beginTxnId, batch5.endTxnId);
    EXPECT_EQ(batch8.endTxnId, match.txnId() + 1);
    EXPECT_THAT(extractDetectionIds(batch8.detectionGroupIds),
        ::testing::UnorderedElementsAre(detections[0].id(), detections[1].id()));

    db::eye::VerifiedDetectionMissingOnFrame missing{
        db::eye::VerificationSource::Toloka,
        detections[0].id(),
        frames[1].id()};
    {
        auto txn = newTxn();
        db::eye::VerifiedDetectionMissingOnFrameGateway{*txn}.insertx(missing);
        txn->commit();
    }

    auto batch9 = getNewBatch(*newTxn(), batch8.endTxnId, 3, detectionTypes);

    EXPECT_EQ(batch9.beginTxnId, batch8.endTxnId);
    EXPECT_EQ(batch9.endTxnId, batch8.endTxnId);
    EXPECT_EQ(extractDetectionIds(batch9.detectionGroupIds).size(), 0u);

    missing.setIsVisible(db::eye::VerifiedDetectionMissingOnFrameIsVisible::No);
    missing.setMissingReason(db::eye::VerifiedDetectionMissingOnFrameMissingReason::Missing);

    {
        auto txn = newTxn();
        db::eye::VerifiedDetectionMissingOnFrameGateway{*txn}.updatex(missing);
        txn->commit();
    }

    auto batch10 = getNewBatch(*newTxn(), batch9.endTxnId, 3, detectionTypes);

    EXPECT_EQ(batch10.beginTxnId, batch9.endTxnId);
    EXPECT_EQ(batch10.endTxnId, missing.txnId() + 1);
    EXPECT_THAT(extractDetectionIds(batch10.detectionGroupIds),
        ::testing::UnorderedElementsAre(detections[0].id()));
}

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