#pragma once

#include "mocks.h"

#include <maps/wikimap/mapspro/services/mrc/eye/lib/common/include/secure_config.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/location.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/object_manager/include/object_manager.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/frame_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/object_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/recognition_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/unittest/include/yandex/maps/mrc/unittest/database_fixture.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/unit_test/include/mds.h>

#include <library/cpp/testing/gtest/gtest.h>

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

inline auto& playground() {
    static unittest::WithUnittestConfig<unittest::DatabaseFixture, MdsStubFixture> global;
    return global;
}

struct BaseFixture : public testing::Test {
    BaseFixture();

    ~BaseFixture() { playground().postgres().truncateTables(); }

    auto& config() const { return playground().config(); }

    auto newTxn() const { return playground().pool().masterWriteableTransaction(); }

    virtual ObjectManagerConfig makeWorkerConfig(db::eye::DetectionTypes detectionTypes) const;

    template<class Gateway, class T>
    auto insertx(std::vector<T> objects) {
        auto txn =  newTxn();
        Gateway(*txn).insertx(objects);
        txn->commit();

        return objects;
    }

    db::eye::Devices devices;
    db::eye::Frames frames;
    db::eye::FrameLocations frameLocations;
    db::eye::FramePrivacies framePrivacies;
    db::eye::DetectionGroups groups;
    db::eye::Detections detections;
    db::eye::Objects objects;
    db::eye::ObjectLocations objectLocations;
    db::eye::PrimaryDetectionRelations relations;
};


/// Fills the database for the following situation
///                       [50] speed_limit
///                       |
///  --------------------------
///  --------------------------
///                       |
///                       [50] speed_limit
/// There are two similar traffic signs located close to each other.
/// They are visible on several passages
struct ObjectClusterizationFixture: public BaseFixture
{

    static constexpr geolib3::Point2 baseMercatorPosition{0, 0};
    static constexpr std::chrono::seconds intervalBetweenPassages{3600};

    struct PassageDetections {
        std::vector<db::eye::Detections> detectionsInFrames;
    };

    std::vector<PassageDetections> detectionsInPassages;

    ObjectClusterizationFixture();

    db::eye::Detections addFrameWithDetections(
        pqxx::transaction_base& txn,
        db::TId deviceId,
        std::chrono::seconds createdAt,
        const geolib3::Vector2& relPosMerc,
        geolib3::Heading heading,
        const std::vector<traffic_signs::TrafficSign> detectionTypes);

    static db::IdTo<db::TIds> loadDetectionClusters(pqxx::transaction_base& txn);

    std::pair<db::eye::Object, db::eye::ObjectLocation> makeSignObject(
        pqxx::transaction_base& txn,
        const db::eye::Detection& primaryDetection,
        const geolib3::Vector2& relPosMerc);

    db::TIds collectDetectionGroupIds(const PassageDetections&) const;
};


struct StraightForwardMatchFixture : public ObjectClusterizationFixture
{
    db::TIdSet firstSignDetections;
    db::TIdSet secondSignDetections;

    StraightForwardMatchFixture()
    {
        for (const auto& passageDetections : detectionsInPassages) {
            for (const auto& detections : passageDetections.detectionsInFrames) {
                firstSignDetections.insert(detections.at(0).id());
                secondSignDetections.insert(detections.at(1).id());
            }
        }
    }

    ObjectManagerConfig makeWorkerConfig(db::eye::DetectionTypes detectionTypes) const override
    {
        auto config = ObjectClusterizationFixture::makeWorkerConfig(detectionTypes);

        config.detectionMatcher = std::make_shared<MockDetectionMatcher>(
            std::vector<db::TIdSet>{firstSignDetections, secondSignDetections}
        );
        return config;
    }
};

std::vector<db::TIdSet> clustersToSets(const db::IdTo<db::TIds>& primaryIdToDetectionIds);

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