#include "fixtures.h"
#include "eye/frame.h"
#include "eye/frame_gateway.h"
#include "mocks.h"

#include <maps/wikimap/mapspro/services/mrc/eye/lib/detection/include/position_matcher.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/detection/include/position_clusterizer.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/unit_test/include/frame.h>

#include <maps/libs/geolib/include/vector.h>
#include <maps/libs/geolib/include/point.h>
#include <maps/libs/geolib/include/heading.h>

#include <chrono>

using namespace std::literals::chrono_literals;

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


BaseFixture::BaseFixture()
{
    // The config content doesn't matter in the tests for now so anyone will fit.
    static std::once_flag onceFlag;
    std::call_once(onceFlag, SecureConfig::initialize, playground().config());
}

ObjectManagerConfig
BaseFixture::makeWorkerConfig(db::eye::DetectionTypes detectionTypes) const
{
    ObjectManagerConfig workerConfig;
    workerConfig.mrc.pool = &playground().pool();
    workerConfig.mrc.commit = true;
    workerConfig.mrc.lockFree = true;
    workerConfig.detectionTypes = std::move(detectionTypes);
    workerConfig.rework = true;
    workerConfig.frameMatcher = std::make_shared<MockFrameMatcher>();
    workerConfig.detectionMatcher = std::make_shared<PositionDetectionMatcher>();
    workerConfig.clusterizer = std::make_shared<PositionDetectionClusterizer>();
    workerConfig.visibilityPredictor = std::make_shared<DummyVisibilityPredictor>();
    auto geoIdProvider = makeMockGeoIdProvider();
    workerConfig.geoIdProvider = geoIdProvider;

    return workerConfig;
}

ObjectClusterizationFixture::ObjectClusterizationFixture()
{
    auto txn = newTxn();

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

    db::eye::DeviceGateway(*txn).insertx(devices);
    size_t passageIdx = 0;
    for (const auto& device : devices) {
        PassageDetections passage;
        const auto passageOffset = passageIdx * intervalBetweenPassages;

        passage.detectionsInFrames.push_back(
            addFrameWithDetections(*txn, device.id(), passageOffset + 0s, {0, 0}, geolib3::Heading{90},
                {traffic_signs::TrafficSign::ProhibitoryMaxSpeed50,
                traffic_signs::TrafficSign::ProhibitoryMaxSpeed50})
        );
        passage.detectionsInFrames.push_back(
            addFrameWithDetections(*txn, device.id(), passageOffset + 3s, {5, 0}, geolib3::Heading{90},
                {traffic_signs::TrafficSign::ProhibitoryMaxSpeed50,
                traffic_signs::TrafficSign::ProhibitoryMaxSpeed50})
        );
        detectionsInPassages.push_back(std::move(passage));
        ++passageIdx;
    }

    txn->commit();
}

db::eye::Detections ObjectClusterizationFixture::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 const auto baseDate = chrono::parseSqlDateTime("2022-02-24 04:00:00+03");
    static const common::ImageOrientation orientation = identical;
    static const auto urlContext = makeUrlContext(1, "1s");
    static const common::Size imageSize{1920, 1080};
    static const common::ImageBox detectionBbox = {1000, 500, 1200, 650};

    db::eye::Frame frame{deviceId, orientation, urlContext, imageSize, baseDate + createdAt};
    db::eye::FrameGateway(txn).insertx(frame);

    db::eye::FrameLocation frameLocation(frame.id(),
        baseMercatorPosition + relPosMerc,
        toRotation(heading, orientation));
    db::eye::FrameLocationGateway(txn).insertx(frameLocation);

    db::eye::FramePrivacy privacy(frame.id(), db::FeaturePrivacy::Public);
    db::eye::FramePrivacyGateway{txn}.insertx(privacy);

    db::eye::DetectionGroup group{frame.id(), db::eye::DetectionType::Sign};
    db::eye::DetectionGroupGateway{txn}.insertx(group);

    db::eye::Detections frameDetections;
    for (auto detectionType : detectionTypes) {
        frameDetections.emplace_back(
            group.id(),
            db::eye::DetectedSign{
                .box = detectionBbox,
                .type = detectionType
            }
        );
    }
    db::eye::DetectionGateway{txn}.insertx(frameDetections);

    frames.push_back(std::move(frame));
    frameLocations.push_back(std::move(frameLocation));
    framePrivacies.push_back(std::move(privacy));
    groups.push_back(std::move(group));
    detections.insert(detections.end(), frameDetections.begin(), frameDetections.end());

    return frameDetections;
}


db::IdTo<db::TIds>
ObjectClusterizationFixture::loadDetectionClusters(pqxx::transaction_base& txn)
{
    db::IdTo<db::TIds> result;

    const auto objects = db::eye::ObjectGateway{txn}
        .load(!db::eye::table::Object::deleted);
    for (const auto& object : objects) {
        result.emplace(object.primaryDetectionId(), db::TIds{});
    }

    const auto primaryDetectionRelations =
        db::eye::PrimaryDetectionRelationGateway{txn}.load(
            !db::eye::table::PrimaryDetectionRelation::deleted
        );
    for (const auto& relation : primaryDetectionRelations) {
        result[relation.primaryDetectionId()].push_back(relation.detectionId());
    }
    return result;
}

db::TIds ObjectClusterizationFixture::collectDetectionGroupIds(
    const PassageDetections& passageDetections) const
{
    db::TIds groupIds;
    for (const auto& detections : passageDetections.detectionsInFrames) {
        groupIds.push_back(detections.at(0).groupId());
    }
    return groupIds;
}

std::pair<db::eye::Object, db::eye::ObjectLocation>
ObjectClusterizationFixture::makeSignObject(
    pqxx::transaction_base& txn,
    const db::eye::Detection& primaryDetection,
    const geolib3::Vector2& relPosMerc)
{
    auto detectionAttrs = primaryDetection.attrs<db::eye::DetectedSign>();
    auto object = db::eye::Object(
        primaryDetection.id(),
        db::eye::SignAttrs{
            .type = detectionAttrs.type,
            .temporary = detectionAttrs.temporary});
    db::eye::ObjectGateway{txn}.insertx(object);

    db::eye::ObjectLocation objectLocation{
        object.id(),
        baseMercatorPosition + relPosMerc,
        Eigen::Quaterniond::Identity()};
    db::eye::ObjectLocationGateway{txn}.insertx(objectLocation);
    return std::make_pair(object, objectLocation);
}

std::vector<db::TIdSet> clustersToSets(const db::IdTo<db::TIds>& primaryIdToDetectionIds)
{
    std::vector<db::TIdSet> result;
    for (const auto& [primaryId, detectionsIds] : primaryIdToDetectionIds) {
        db::TIdSet idSet;
        idSet.insert(primaryId);
        idSet.insert(detectionsIds.begin(), detectionsIds.end());
        result.push_back(std::move(idSet));
    }
    return result;
}

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