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

#include <maps/wikimap/mapspro/services/mrc/eye/lib/import_mrc/include/import.h>

#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/move.h>
#include <maps/wikimap/mapspro/services/mrc/eye/lib/location/include/rotation.h>

#include <maps/libs/json/include/value.h>

#include <algorithm>
#include <iostream>

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

namespace {

constexpr common::Size size {1280, 720};

db::Features makeFeatures(pgpool3::Pool& pool)
{
    auto features = db::Features{
        /* Already imported features */
        /* 0 */ db::Feature{"1",
                            geolib3::Point2{31, 10},
                            geolib3::Heading{12},
                            "2016-04-01 05:57:09+03",
                            mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                            db::Dataset::Agents},
        /* 1 */
        db::Feature{"1",
                    geolib3::Point2{10, 50},
                    geolib3::Heading{90},
                    "2016-04-01 05:58:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 2 */
        db::Feature{"2",
                    geolib3::Point2{12, 87},
                    geolib3::Heading{67},
                    "2016-04-01 05:59:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 3 */
        db::Feature{"2",
                    geolib3::Point2{12, 87},
                    geolib3::Heading{67},
                    "2016-04-01 06:00:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},

        /* frameNew features */
        /* 4 */
        db::Feature{"3",
                    geolib3::Point2{-7, -7},
                    geolib3::Heading{-5},
                    "2016-04-01 06:30:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 5 */
        db::Feature{"4",
                    geolib3::Point2{-4, 14},
                    geolib3::Heading{-9},
                    "2016-04-01 05:57:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 6 */
        db::Feature{"4",
                    geolib3::Point2{10, -5},
                    geolib3::Heading{0},
                    "2016-04-01 05:57:10+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 7 */
        db::Feature{"5",
                    geolib3::Point2{2, 2},
                    geolib3::Heading{90},
                    "2016-04-01 05:57:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 8 */
        db::Feature{"5",
                    geolib3::Point2{3, 3},
                    geolib3::Heading{90},
                    "2016-04-01 05:57:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 9 */
        db::Feature{"5",
                    geolib3::Point2{4, 4},
                    geolib3::Heading{180},
                    "2016-04-01 05:57:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "M1_CW0"},
                    db::Dataset::Agents},
        /* 10 */
        db::Feature{"6",
                    geolib3::Point2{5, 5},
                    geolib3::Heading{180},
                    "2016-04-01 05:57:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "no_model_attr"},
                    db::Dataset::Agents},
        /* 11 */
        db::Feature{"7",
                    geolib3::Point2{6, 6},
                    geolib3::Heading{180},
                    "2016-04-01 05:57:09+03",
                    mds::Key{unittest::MDS_GROUP_ID, "no_image"},
                    db::Dataset::Agents},
    };

    for (auto& feature : features) {
        feature.setOrientation(common::ImageOrientation(common::Rotation::CW_0))
            .setSize(size)
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    }

    features[5].setCameraRodrigues({0, 0, 0});
    features[7].setAutomaticShouldBePublished(false);

    auto insert = [&](size_t begin, size_t end)
    {
        auto txn = pool.masterWriteableTransaction();

        TArrayRef ref(features.data() + begin, features.data() + end);

        db::FeatureGateway(*txn).insert(ref);
        db::updateFeaturesTransaction(ref, *txn);

        txn->commit();
    };

    // Insert in different transactions
    insert(0, 4);
    insert(4, 5);
    insert(5, 7);
    insert(7, 10);
    insert(10, 12);

    return features;
}

db::eye::MrcUrlContext makeUrlContext(const db::Feature& feature)
{
    return db::eye::MrcUrlContext{
        feature.id(), feature.mdsGroupId(), feature.mdsPath()};
}

template<class Gateway, class Map = std::map<typename Gateway::Id, typename Gateway::Entity>>
Map loadMap(pqxx::transaction_base& txn)
{
    Map result;

    Gateway gateway(txn);

    for (auto& object: gateway.load()) {
        result.emplace(gateway.id(object), object);
    }

    return result;
}

} // namespace

Fixture::Fixture()
: features(makeFeatures(playground().pool()))
, mds(config().makeMdsClient())
{
    auto txn = newTxn();

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

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

    std::vector<db::eye::FeatureSourceToDevice> deviceMatching {
        {"1", devices[0].id()},
        {"2", devices[1].id()},
    };

    db::eye::FeatureSourceToDeviceGateway(*txn).insert(deviceMatching);

    auto deviceId = [&](const std::string& sourceId)
    {
        const auto it = std::find_if(
            deviceMatching.begin(), deviceMatching.end(),
            [&](const auto& pair) {
                return pair.sourceId() == sourceId;
            }
        );

        ASSERT(it != deviceMatching.end());
        return it->deviceId();
    };

    constexpr size_t frameN = 4;

    db::eye::Frames frames;
    for (size_t i = 0; i < frameN; ++i) {
        const auto& feature = features.at(i);

        frames.emplace_back(
            deviceId(feature.sourceId()),
            feature.orientation(),
            makeUrlContext(feature),
            size,
            feature.timestamp()
        );
    }

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

    std::vector<db::eye::FeatureToFrame> frameMatching;
    for (size_t i = 0; i < frameN; ++i) {
        frameMatching.emplace_back(features.at(i).id(), frames.at(i).id());
    }

    db::eye::FeatureToFrameGateway(*txn).insert(frameMatching);

    db::eye::FrameLocations locations;
    for (size_t i = 0; i < frameN; ++i) {
        const auto& feature = features.at(i);

        locations.emplace_back(
            frames[i].id(),
            feature.mercatorPos(),
            toRotation(feature.heading(), feature.orientation()),
            toMoveVector(feature.heading())
        );
    }

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

    db::eye::FramePrivacies privacies;
    for (size_t i = 0; i < frameN; ++i) {
        privacies.emplace_back(frames[i].id(), features.at(i).privacy());
    }

    db::eye::FramePrivacyGateway(*txn).insertx(privacies);

    txn->commit();
}

Fixture::~Fixture() {
    playground().postgres().truncateTables();
}

db::TId Fixture::featureIdAt(size_t index) const
{
    return features.at(index).id();
}

db::TIds Fixture::featureIdsAt(std::initializer_list<size_t> indexes) const
{
    db::TIds ids;

    for (size_t i: indexes) {
        ids.push_back(features.at(i).id());
    }

    return ids;
}

std::set<db::TId> Fixture::frameIdSetAt(std::initializer_list<size_t> indexes)
{
    const auto pairs = db::eye::FeatureToFrameGateway(*newTxn()).loadByIds(featureIdsAt(indexes));

    std::set<db::TId> frameIdSet;
    for (const auto& pair: pairs) {
        frameIdSet.insert(pair.frameId());
    };

    return frameIdSet;
}

std::map<std::string, db::eye::FeatureSourceToDevice> Fixture::loadDeviceMatching()
{
    return loadMap<db::eye::FeatureSourceToDeviceGateway>(*newTxn());
}

std::map<db::TId, db::eye::FeatureToFrame> Fixture::loadFrameMatching()
{
    return loadMap<db::eye::FeatureToFrameGateway>(*newTxn());
}

std::map<db::TId, db::eye::Device> Fixture::loadDeviceMap()
{
    return loadMap<db::eye::DeviceGateway>(*newTxn());
}

std::map<db::TId, db::eye::Frame> Fixture::loadFrameMap()
{
    return loadMap<db::eye::FrameGateway>(*newTxn());
}

std::map<db::TId, db::eye::FrameLocation> Fixture::loadFrameLocationMap()
{
    return loadMap<db::eye::FrameLocationGateway>(*newTxn());
}

std::map<db::TId, db::eye::FramePrivacy> Fixture::loadFramePrivacyMap()
{
    return loadMap<db::eye::FramePrivacyGateway>(*newTxn());
}

db::TId Fixture::getDeviceId(const std::string& sourceId)
{
    return db::eye::FeatureSourceToDeviceGateway(*newTxn()).loadById(sourceId).deviceId();
}

db::TId Fixture::getFrameId(db::TId featureId)
{
    return db::eye::FeatureToFrameGateway(*newTxn()).loadById(featureId).frameId();
}

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