#include "construct.h"
#include "fixture.h"

#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/introspection/include/comparison.h>
#include <maps/libs/introspection/include/stream_output.h>
#include <maps/libs/sql_chemistry/include/system_information.h>
#include <maps/libs/tile/include/utils.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/toloka/task_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/ugc/gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/visibility.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/walk_object_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/unittest/include/yandex/maps/mrc/unittest/database_fixture.h>
#include <maps/wikimap/mapspro/services/mrc/libs/unittest/include/yandex/maps/mrc/unittest/log_disabling_fixture.h>
#include <yandex/maps/geolib3/sproto.h>
#include <maps/libs/geolib/include/test_tools/io_operations.h>

#include <boost/optional/optional_io.hpp>

#include <random>

namespace std {
std::ostream& operator<<(std::ostream& os, const maps::chrono::TimePoint& tp) {
    return os << maps::chrono::formatSqlDateTime(tp);
}
} // namespace std

namespace maps::geolib3 {
using io::operator<<;
} // namespace maps::geolib3

namespace maps::mrc::db {
using introspection::operator==;
using introspection::operator!=;
using introspection::operator<<;
} // namespace maps::mrc::db

namespace maps::mrc::db::tests {
using namespace ::testing;
using geolib3::Point2;
using geolib3::Heading;


TEST_F(Fixture, test_features) {

    auto DATA = Features{
        Feature{
            "src1",
            Point2{43.999, 56.322 - geolib3::EPS},
            Heading{360.0 - geolib3::EPS},
            "2016-04-01 05:57:09+03",
            mds::Key{"4510", "1460732825/MRC_20160401_085709_1010624788.jpg"},
            Dataset::Rides},
        Feature{
            "src2",
            Point2{43.9992, 56.3218},
            Heading{111.71},
            "2016-04-02 05:57:11+03",
            mds::Key{"4510", "1460732825/MRC_20160401_085711_1545540516.jpg"},
            Dataset::Agents},
        Feature{
            "src1",
            Point2{43.9996, 56.3218},
            Heading{93.2133},
            "2016-04-01 05:57:14+03",
            mds::Key{"4510", "1460732825/MRC_20160401_085714_319880659.jpg"},
            Dataset::Agents},
        Feature{
            "src2",
            Point2{43.9997, 56.3218},
            Heading{70.0535},
            "2016-04-02 05:57:16+03",
            mds::Key{"4510", "1460732825/MRC_20160401_085716_-537850384.jpg"},
            Dataset::Agents},
        Feature{
            "src1",
            Point2{43.9999, 56.3219},
            Heading{78.2299},
            "2016-04-01 05:57:18+03",
            mds::Key{"4510", "1460732825/MRC_20160401_085718_-1657768016.jpg"},
            Dataset::Agents},
        Feature{
            "src2",
            Point2{44, 56.3219},
            Heading{116.926},
            "2016-04-02 05:57:20+03",
            mds::Key{"4510", "1460732825/MRC_20160401_085720_-1715123755.jpg"},
            Dataset::Agents},
        Feature{
            "src1",
            Point2{44, 56.3219},
            Heading{31.9262},
            "2016-04-01 05:57:22+03",
            mds::Key{"4436", "1460732825/MRC_20160401_085722_272802758.jpg"},
            Dataset::Agents}};

    for (auto& feature : DATA) {
        feature.setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    }

    TIds ids;
    size_t count = 0;

    Features features = DATA;
    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        gtw.insert(features);
        for (const auto& feature : features) {
            ids.push_back(feature.id());
        }

        auto nextFeature =
            Feature{"src2",
                    Point2{44.0001, 56.3219},
                    Heading{116.346},
                    "2016-04-02 05:57:25+03",
                    mds::Key{"4436",
                             "1460732825/MRC_20160401_085725_-1584139459.jpg"},
                    Dataset::Walks}
                .setSize({6, 9})
                .setAutomaticShouldBePublished(true)
                .setIsPublished(true);
        gtw.insert(nextFeature);
        features.push_back(nextFeature);
        for (const auto& feature : features) {
            ids.push_back(feature.id());
        }
        txn->commit();
        count = features.size();
    }

    {
        const std::string SOURCE_ID = "src1";

        auto txn = txnHandle();
        FeatureGateway gtw{*txn};

        EXPECT_EQ(gtw.loadByIds(ids).size(), count);

        TIds expectedIds;
        for (size_t i = 0; i < DATA.size(); ++i) {
            auto feature = gtw.loadById(ids[i]);
            EXPECT_EQ(feature, features[i]);
            EXPECT_TRUE(feature.isPublished());
            if (feature.sourceId() == SOURCE_ID) {
                expectedIds.push_back(feature.id());
            }
        }

        geolib3::BoundingBox bbox{{43.9996, 56.3218}, {43.9999, 56.3219}};
        auto featuresIntersects = gtw.load(table::Feature::isPublished &&
            table::Feature::pos.intersects(geolib3::convertGeodeticToMercator(bbox)));
        EXPECT_EQ(featuresIntersects.size(), 3u);

        EXPECT_TRUE(gtw.exists(table::Feature::isPublished &&
            table::Feature::pos.intersects(tile::Tile(81555, 40720, 17))));
        EXPECT_FALSE(gtw.exists(table::Feature::isPublished &&
            table::Feature::pos.intersects(tile::Tile(81556, 40720, 17))));
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto feature = gtw.loadById(ids.back());
        feature
            .setSourceId("src3")
            .setGeodeticPos(Point2{44, 56.322})
            .setHeading(Heading{116.0})
            .setTimestamp("2016-04-03 05:57:25+03")
            .setMdsKey({"4436", "1460732825/MRC_20160401_085725_-1584139459.jpg"})
            .setDataset(Dataset::Agents)
            .setIsPublished(true);
        gtw.update(feature, UpdateFeatureTxn::No);

        auto loaded = gtw.loadById(ids.back());
        EXPECT_EQ(loaded, feature);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto featuresIds = gtw.loadIds();
        std::set<TId> originalIdSet(std::begin(ids), std::end(ids));
        std::set<TId> resultIdSet(std::begin(featuresIds),
            std::end(featuresIds));
        EXPECT_EQ(originalIdSet, resultIdSet);
    }

    const auto featureId = ids.front();
    const size_t WIDTH = 12;
    const size_t HEIGHT = 9;
    const double QUALITY = 0.5;
    const double ROAD_PROBABILITY = 0.25;
    const double FORBIDDEN_PROBABILITY = 0.75;
    const auto ORIENTATION = common::ImageOrientation::fromExif(3);
    const auto CAMERA_DEVIATION = CameraDeviation::Right;
    const auto PRIVACY = FeaturePrivacy::Restricted;
    const auto GRAPH = GraphType::Pedestrian;
    const auto UPLOADED_AT = chrono::parseSqlDateTime("2016-04-04 18:57:00+03");
    const bool MODERATORS_SHOULD_BE_PUBLISHED = true;
    const geolib3::Point2 ODOMETER_POS(43.123456, 56.123456);
    const std::vector<double> CAMERA_RODRIGUES {1.0, 2.0, 3.0};
    const std::string USER_ID = "123456";
    const bool GDPR_DELETED = false;
    const bool SHOW_AUTHORSHIP = true;
    const bool DELETED_BY_USER = false;
    const bool AUTOMATIC_SHOULD_BE_PUBLISHED = false;
    const auto PROCESSED_AT = chrono::parseSqlDateTime("2021-05-19 11:22:00+03");
    const auto CLIENT_RIDE_ID = std::string("b0c2d8c8-6fc6-45d0-9e8e-45e37bd29636");
    const auto DELETED_FROM_MDS = false;

    auto task = ugc::Task{}
                    .setStatus(ugc::TaskStatus::New)
                    .setDuration(std::chrono::hours{24})
                    .setDistanceInMeters(100500)
                    .setGeodeticHull(geolib3::MultiPolygon2{});
    auto assignment = sql_chemistry::GatewayAccess<ugc::Assignment>::construct();
    auto object = WalkObject{Dataset::Walks,
                             "src1",
                             chrono::parseSqlDateTime("2021-03-19 12:20:00+03"),
                             WalkFeedbackType::None,
                             geolib3::Point2{},
                             "comment"};

    {
        auto txn = txnHandle();

        ugc::TaskGateway{*txn}.insert(task);
        assignment = task.assignTo(USER_ID);
        ugc::TaskGateway{*txn}.update(task);
        ugc::AssignmentGateway{*txn}.insert(assignment);
        WalkObjectGateway{*txn}.insertx(object);

        FeatureGateway gtw{*txn};
        auto feature = gtw.loadById(featureId);
        EXPECT_FALSE(feature.hasQuality());
        EXPECT_FALSE(feature.hasRoadProbability());
        EXPECT_FALSE(feature.hasForbiddenProbability());
        EXPECT_TRUE(feature.isPublished());
        EXPECT_FALSE(feature.hasOrientation());
        EXPECT_FALSE(feature.hasCameraDeviation());
        EXPECT_FALSE(feature.hasPrivacy());
        EXPECT_FALSE(feature.hasGraph());
        EXPECT_FALSE(feature.hasUploadedAt());
        EXPECT_FALSE(feature.moderatorsShouldBePublished().has_value());
        EXPECT_FALSE(feature.assignmentId().has_value());
        EXPECT_FALSE(feature.userId().has_value());
        EXPECT_FALSE(feature.gdprDeleted().has_value());
        EXPECT_FALSE(feature.showAuthorship().has_value());
        EXPECT_FALSE(feature.deletedByUser().has_value());
        EXPECT_FALSE(feature.walkObjectId().has_value());
        EXPECT_TRUE(feature.automaticShouldBePublished().has_value());
        EXPECT_NE(feature.automaticShouldBePublished().value(),
                  AUTOMATIC_SHOULD_BE_PUBLISHED);
        EXPECT_FALSE(feature.processedAt().has_value());
        EXPECT_FALSE(feature.clientRideId().has_value());
        EXPECT_FALSE(feature.deletedFromMds().has_value());

        feature.setSize({WIDTH, HEIGHT})
            .setQuality(QUALITY)
            .setRoadProbability(ROAD_PROBABILITY)
            .setForbiddenProbability(FORBIDDEN_PROBABILITY)
            .setIsPublished(false)
            .setOrientation(ORIENTATION)
            .setCameraDeviation(CAMERA_DEVIATION)
            .setPrivacy(PRIVACY)
            .setGraph(GRAPH)
            .setUploadedAt(UPLOADED_AT)
            .setModeratorsShouldBePublished(MODERATORS_SHOULD_BE_PUBLISHED)
            .setOdometerMercatorPos(ODOMETER_POS)
            .setCameraRodrigues(CAMERA_RODRIGUES)
            .setAssignmentId(assignment.id())
            .setUserId(USER_ID)
            .setGdprDeleted(GDPR_DELETED)
            .setShowAuthorship(SHOW_AUTHORSHIP)
            .setDeletedByUser(DELETED_BY_USER)
            .setWalkObjectId(object.id())
            .setAutomaticShouldBePublished(AUTOMATIC_SHOULD_BE_PUBLISHED)
            .setProcessedAt(PROCESSED_AT)
            .setClientRideId(CLIENT_RIDE_ID)
            .setDeletedFromMds(DELETED_FROM_MDS)
            ;
        gtw.update(feature, UpdateFeatureTxn::No);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto feature = gtw.loadById(featureId);
        EXPECT_EQ(feature.size().width, WIDTH);
        EXPECT_EQ(feature.size().height, HEIGHT);
        EXPECT_NEAR(feature.quality(), QUALITY, 0.001);
        EXPECT_NEAR(feature.roadProbability(), ROAD_PROBABILITY, 0.001);
        EXPECT_NEAR(feature.forbiddenProbability(), FORBIDDEN_PROBABILITY, 0.001);
        EXPECT_FALSE(feature.isPublished());
        EXPECT_EQ(feature.orientation(), ORIENTATION);
        EXPECT_EQ(feature.cameraDeviation(), CAMERA_DEVIATION);
        EXPECT_EQ(feature.privacy(), PRIVACY);
        EXPECT_EQ(feature.graph(), GRAPH);
        EXPECT_TRUE(feature.hasUploadedAt());
        EXPECT_EQ(feature.uploadedAt(), UPLOADED_AT);
        EXPECT_TRUE(feature.moderatorsShouldBePublished().has_value());
        EXPECT_EQ(feature.moderatorsShouldBePublished(),
                  MODERATORS_SHOULD_BE_PUBLISHED);
        EXPECT_EQ(feature.odometerMercatorPos(), ODOMETER_POS);
        EXPECT_EQ(feature.cameraRodrigues(), CAMERA_RODRIGUES);
        EXPECT_TRUE(feature.assignmentId().has_value());
        EXPECT_EQ(feature.assignmentId().value(), assignment.id());
        EXPECT_TRUE(feature.userId().has_value());
        EXPECT_EQ(feature.userId().value(), USER_ID);
        EXPECT_TRUE(feature.gdprDeleted().has_value());
        EXPECT_EQ(feature.gdprDeleted().value(), GDPR_DELETED);
        EXPECT_TRUE(feature.showAuthorship().has_value());
        EXPECT_EQ(feature.showAuthorship().value(), SHOW_AUTHORSHIP);
        EXPECT_TRUE(feature.deletedByUser().has_value());
        EXPECT_EQ(feature.deletedByUser().value(), DELETED_BY_USER);
        EXPECT_TRUE(feature.walkObjectId().has_value());
        EXPECT_EQ(feature.walkObjectId().value(), object.id());
        EXPECT_TRUE(feature.automaticShouldBePublished().has_value());
        EXPECT_EQ(feature.automaticShouldBePublished().value(),
                  AUTOMATIC_SHOULD_BE_PUBLISHED);
        EXPECT_EQ(feature.processedAt(), PROCESSED_AT);
        EXPECT_EQ(feature.clientRideId(), CLIENT_RIDE_ID);
        EXPECT_TRUE(feature.deletedFromMds().has_value());
        EXPECT_EQ(feature.deletedFromMds().value(), DELETED_FROM_MDS);
        auto bbox = geolib3::resizeByValue(feature.mercatorPos().boundingBox(), geolib3::EPS);
        auto features = gtw.load(
            table::Feature::isPublished && table::Feature::pos.intersects(bbox));
        EXPECT_TRUE(features.empty()); // unpublished
    }
}

TEST_F(Fixture, test_create_remove_features) {

          auto DATA = Features{Feature{"src1",
                                       Point2{44.01, 56.30},
                                       Heading{10.0},
                                       "2016-09-01 05:10:00+03",
                                       mds::Key{"4510", "photo1.jpg"},
                                       Dataset::Agents},
                               Feature{"src2",
                                       Point2{44.01, 56.31},
                                       Heading{20.0},
                                       "2016-09-01 05:20:00+03",
                                       mds::Key{"4510", "photo2.jpg"},
                                       Dataset::Agents},
                               Feature{"src1",
                                       Point2{44.02, 56.32},
                                       Heading{30.0},
                                       "2016-09-01 05:30:00+03",
                                       mds::Key{"4510", "photo3.jpg"},
                                       Dataset::Agents},
                               Feature{"src2",
                                       Point2{44.03, 56.33},
                                       Heading{40.0},
                                       "2016-09-01 05:40:00+03",
                                       mds::Key{"4510", "photo4.jpg"},
                                       Dataset::Agents}};

    for (auto& feature : DATA) {
        feature.setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    }

    Features features;
    TIds ids;

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};

        for (const auto& tuple : DATA) {
            features.push_back(Feature(tuple));
        }
        gtw.insert(features);
        for (const auto& feature : features) {
            ids.push_back(feature.id());
        }
        txn->commit();
    }

    TId id;
    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};

        EXPECT_EQ(gtw.loadIds().size(), features.size());

        // remove all features but the last one
        id = ids.back();
        ids.pop_back();
        gtw.removeByIds(ids);
        txn->commit();
    }

    auto txn = txnHandle();
    FeatureGateway gtw{*txn};
    auto remainedIds = gtw.loadIds();
    EXPECT_EQ(remainedIds.size(), 1u);
    EXPECT_EQ(remainedIds.front(), id);
}

/// @see https://st.yandex-team.ru/NMAPS-5499
TEST_F(Fixture, test_feature_path_with_equal_timestamp) {

    auto features = Features{
        Feature{"abc",
                {30, 59},
                Heading{344},
                "2016-06-11 11:21:36",
                {"4510", "photo1.jpg"},
                Dataset::Agents},
        Feature{"abc",
                {30, 59},
                Heading{344},
                "2016-06-11 11:21:36",
                {"4510", "photo2.jpg"},
                Dataset::Agents},
        Feature{"abc",
                {30, 59},
                Heading{344},
                "2016-06-11 11:21:37",
                {"4510", "photo3.jpg"},
                Dataset::Agents},
        Feature{"abc",
                {30, 59},
                Heading{344},
                "2016-06-11 11:21:37",
                {"4510", "photo4.jpg"},
                Dataset::Agents},
        Feature{"abc",
                {30, 59},
                Heading{344},
                "2016-06-11 11:21:38",
                {"4510", "photo5.jpg"},
                Dataset::Agents},
        Feature{"abc",
                {30, 59},
                Heading{344},
                "2016-06-11 11:21:38",
                {"4510", "photo6.jpg"},
                Dataset::Agents},
    };

    for (auto& feature : features) {
        feature.setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    }

    {
        auto txn = txnHandle();
        FeatureGateway{*txn}.insert(features);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto loadSeqIds = [&](TId id) {
            TIds ids;
            for (auto& feature: gtw.loadSequence(id, 2, 2)) { ids.push_back(feature.id()); }
            return ids;
        };
        EXPECT_THAT(loadSeqIds(0), ElementsAre());
        EXPECT_THAT(loadSeqIds(1), ElementsAre(1, 2, 3));
        EXPECT_THAT(loadSeqIds(2), ElementsAre(1, 2, 3));
        EXPECT_THAT(loadSeqIds(3), ElementsAre(1, 2, 3, 4, 5));
        EXPECT_THAT(loadSeqIds(4), ElementsAre(1, 2, 3, 4, 5));
        EXPECT_THAT(loadSeqIds(5), ElementsAre(3, 4, 5, 6));
        EXPECT_THAT(loadSeqIds(6), ElementsAre(3, 4, 5, 6));
        EXPECT_THAT(loadSeqIds(7), ElementsAre());
    }
}

/// @see https://st.yandex-team.ru/MAPSMRC-1078
TEST_F(Fixture, test_feature_path_with_inverse_id_order) {

    std::vector<Feature> features;
    std::string sourceId = "49a40c4052d2d546977b21110621e7ac ";
    {
        chrono::TimePoint tp = chrono::parseSqlDateTime("2018-06-13 18:09:49.559+03");

        for (size_t i = 0; i < 20; ++i) {
            tp += std::chrono::seconds(1);

            features.push_back(
                newFeature()
                    .setHeading(Heading{100})
                    .setGeodeticPos({10, 20})
                    .setSize({6, 9})
                    .setAutomaticShouldBePublished(true)
                    .setIsPublished(true)
                    .setTimestamp(tp)
                    .setMdsKey({"1", "photo"})
                    .setDataset(Dataset::Agents)
                    .setSourceId(sourceId)
            );
        }

        // Make features ordered by time but not by id.
        std::shuffle(features.begin(), features.end(), std::mt19937(42));
        auto txn = txnHandle();
        FeatureGateway{*txn}.insert(features);
        txn->commit();
        std::sort(features.begin(), features.end(),
            [](auto& f1, auto& f2) { return f1.timestamp() < f2.timestamp(); });
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        // 7 features in sequence, 3 before, 1 base, 3 after.
        auto loadSeqIds = [&](size_t idIdx) {
            TIds res;
            auto id = features.at(idIdx).id();
            for (auto& f: gtw.loadSequence(id, 3, 3)) { res.push_back(f.id()); }
            return res;
        };
        auto idsRange = [&](size_t beginIdx, size_t endIdx) {
            TIds res;
            for (size_t i = beginIdx; i <= endIdx; ++i) { res.push_back(features.at(i).id()); }
            return res;
        };
        long sz = static_cast<long>(features.size());
        for (long i = 0; i < sz; ++i) {
            EXPECT_THAT(loadSeqIds(i), ElementsAreArray(
                idsRange(std::max(0l, i - 3), std::min(sz - 1, i + 3))));
        }
    }
}

TEST_F(Fixture, test_loading_features_by_filter) {

    auto TEST_FEATURES = Features{
        Feature{"source_id_1",
                Point2{0.0, 0.0},
                Heading{180.0},
                "2016-06-11 10:00:00",
                mds::Key{"4510", "photo1.jpg"},
                Dataset::Agents},
        Feature{"source_id_1",
                Point2{0.0, 0.0},
                Heading{180.0},
                "2016-06-11 11:00:00",
                mds::Key{"4510", "photo2.jpg"},
                Dataset::Agents},
        Feature{"source_id_2",
                Point2{0.0, 0.0},
                Heading{180.0},
                "2016-06-11 12:00:00",
                mds::Key{"4510", "photo3.jpg"},
                Dataset::Agents},
    };

    for (auto& feature : TEST_FEATURES) {
        feature.setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        gtw.insert(TEST_FEATURES);
        txn->commit();
    }

    {
        const auto TEST_TIME_POINT
            = chrono::parseIsoDateTime("2016-06-11 10:30:00");
        const auto TEST_SOURCE_ID = "source_id_1";
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        Features features;

        features
            = gtw.load(
                table::Feature::isPublished &&
                table::Feature::sourceId.equals(TEST_SOURCE_ID));
        EXPECT_EQ(features.size(), 2u);
        for (const auto& f : features) {
            EXPECT_EQ(f.sourceId(), TEST_SOURCE_ID);
        }

        features
            = gtw.load(
                table::Feature::isPublished &&
                table::Feature::date.greater(TEST_TIME_POINT),
                sql_chemistry::limit(1));
        EXPECT_EQ(features.size(), 1u);

        for (const auto& f : features) {
            EXPECT_TRUE(f.timestamp() > TEST_TIME_POINT);
        }

        features = gtw.load(
            table::Feature::isPublished
            && table::Feature::date.greater(TEST_TIME_POINT)
            && table::Feature::sourceId.equals(TEST_SOURCE_ID));
        EXPECT_EQ(features.size(), 1u);
        EXPECT_GT(features.front().timestamp(), TEST_TIME_POINT);
        EXPECT_EQ(features.front().sourceId(), TEST_SOURCE_ID);

        features = gtw.load(table::Feature::shouldBePublished.is(true));
        EXPECT_EQ(features.size(), 3u);
        EXPECT_TRUE(features.front().shouldBePublished());
    }
}

TEST_F(Fixture, test_ride_photo) {

          auto DATA = Features{Feature{"src1",
                                       Point2{44.01, 56.30},
                                       Heading{10.0},
                                       "2016-09-01 05:10:00+03",
                                       mds::Key{"4510", "photo1.jpg"},
                                       Dataset::Agents}
                                   .setUserId("Helg"),
                               Feature{"src2",
                                       Point2{44.01, 56.31},
                                       Heading{20.0},
                                       "2016-09-01 05:20:00+03",
                                       mds::Key{"4510", "photo2.jpg"},
                                       Dataset::Agents}
                                   .setUserId("Ingvar")};

    for (auto& feature : DATA) {
        feature.setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    }

    TIds ids;
    Features ridePhotos = DATA;
    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        gtw.insert(ridePhotos);
        for (const auto& ridePhoto : ridePhotos) {
            ids.push_back(ridePhoto.id());
        }
        txn->commit();
    }
    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        EXPECT_EQ(gtw.loadIds().size(), ids.size());
        for (size_t i = 0; i < ids.size(); ++i) {
            auto ridePhoto = gtw.loadById(ids[i]);
            EXPECT_EQ(ridePhoto, ridePhotos[i]);
        }
    }

    TId remainedId;
    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        remainedId = ids.back();
        ids.pop_back();
        gtw.removeByIds(ids);
        txn->commit();
    }
    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto remainedIds = gtw.loadIds();
        EXPECT_EQ(remainedIds.size(), 1UL);
        EXPECT_EQ(remainedIds.front(), remainedId);
    }

    constexpr double QUALITY = 0.99;
    constexpr bool GDPR_DELETED = true;
    constexpr bool SHOW_AUTHORSHIP = false;
    constexpr bool DELETED_BY_USER = true;

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto feature = gtw.loadById(remainedId);
        feature.setQuality(QUALITY);
        gtw.update(feature, UpdateFeatureTxn::No);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto ridePhoto = gtw.loadById(remainedId);
        ridePhoto.setGdprDeleted(GDPR_DELETED);
        ridePhoto.setShowAuthorship(SHOW_AUTHORSHIP);
        ridePhoto.setDeletedByUser(DELETED_BY_USER);
        gtw.update(ridePhoto, UpdateFeatureTxn::No);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        FeatureGateway gtw{*txn};
        auto ridePhoto = gtw.loadById(remainedId);
        EXPECT_NEAR(QUALITY, ridePhoto.quality(), 0.001);
        EXPECT_EQ(GDPR_DELETED, ridePhoto.gdprDeleted());
        EXPECT_EQ(SHOW_AUTHORSHIP, ridePhoto.showAuthorship());
        EXPECT_EQ(DELETED_BY_USER, ridePhoto.deletedByUser());
    }
}

TEST_F(Fixture, test_feature_qa_task) {


    TId featureId = 0;
    TId tolokaId = 0;
    {
        auto txn = txnHandle();

        Features features;
        features
            .emplace_back(
                "src1",
                Point2{43.999, 56.3219},
                Heading{359.999},
                "2016-04-01 05:57:09+03",
                mds::Key{"4510",
                         "1460732825/MRC_20160401_085709_1010624788.jpg"},
                Dataset::Agents)
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
        FeatureGateway{*txn}.insert(features);
        featureId = features.front().id();

        toloka::Task task(toloka::Platform::Toloka);
        task.setType(toloka::TaskType::ImageQualityClassification)
            .setStatus(toloka::TaskStatus::InProgress)
            .setInputValues("input-values")
            .setOverlap(3)
            .setCreatedAt(chrono::TimePoint::clock::now())
            .setCreatedAt(chrono::TimePoint::clock::now());
        toloka::TaskGateway{*txn}.insertx(task);
        tolokaId = task.id();

        FeatureQaTasks tasks;
        tasks.emplace_back(featureId, tolokaId);
        FeatureQaTaskGateway{*txn}.insert(tasks);

        txn->commit();
    }

    {
        auto txn = txnHandle();

        auto tasks = FeatureQaTaskGateway{*txn}.load(
            table::FeatureQaTask::featureId.equals(featureId));
        EXPECT_EQ(tasks.size(), 1u);
        EXPECT_EQ(tasks.front().featureId(), featureId);
        EXPECT_EQ(tasks.front().tolokaId(), tolokaId);
    }
}

TEST_F(Fixture, test_feature_transaction_update_feature_table) {


    auto features = Features{
        Feature{
            "src1",
            Point2{43.999, 56.3219},
            Heading{359.999},
            "2016-04-01 05:57:09+03",
            mds::Key{"4510", "1460732825/MRC_20160401_085709_1010624788.jpg"},
            Dataset::Agents}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),
    };

    db::TId transactionId = 0;

    {   // insert feature
        auto txn = txnHandle();

        FeatureGateway{*txn}.insert(features);

        txn->commit();
    }

    {   // update feature
        auto txn = txnHandle();
        transactionId = sql_chemistry::SystemInformation(*txn).getCurrentTransactionId();

        features[0].setHeading(Heading(359.999));
        FeatureGateway{*txn}.update(features, UpdateFeatureTxn::Yes);
        txn->commit();
    }

    {   // check transaction id is the same (there is no trigger to update it)
        auto txn = txnHandle();

        const FeatureTransaction featureTransaction = FeatureTransactionGateway(*txn).loadOne(
            table::FeatureTransaction::featureId.equals(features[0].id())
        );

        EXPECT_EQ(featureTransaction.transactionId(), transactionId);
    }

    {   // update feature without updating feature_transaction
        auto txn = txnHandle();
        features[0].setQuality(0.9);
        FeatureGateway{*txn}.update(features, UpdateFeatureTxn::No);
        txn->commit();
    }

    {   // check transaction id has not changed
        auto txn = txnHandle();
        const FeatureTransaction featureTransaction = FeatureTransactionGateway(*txn).loadOne(
            table::FeatureTransaction::featureId.equals(features[0].id())
        );
        EXPECT_EQ(featureTransaction.transactionId(), transactionId);
    }
}

TEST_F(Fixture, test_feature_transaction_update_ride_photo_table) {


    auto ridePhotos = Features{
        Feature{"src1",
                Point2{44.01, 56.30},
                Heading{10.0},
                "2016-09-01 05:10:00+03",
                mds::Key{"4510", "photo1.jpg"},
                Dataset::Agents}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true)
            .setUserId("Helg"),
    };

    TId featureId = 0;
    TId transactionId = 0;

    {   // create ride photo
        auto txn = txnHandle();
        FeatureGateway{*txn}.insert(ridePhotos);

        txn->commit();
        featureId = ridePhotos[0].id();
    }

    {   // update ride photo
        auto txn = txnHandle();
        transactionId = sql_chemistry::SystemInformation(*txn).getCurrentTransactionId();
        auto features = FeatureGateway{*txn}.loadByIds({featureId});

        features[0].setHeading(Heading{15.0});
        FeatureGateway{*txn}.update(features, UpdateFeatureTxn::Yes);
        txn->commit();
    }

    {   // check new transaction id
        auto txn = txnHandle();

        const FeatureTransaction featureTransaction = FeatureTransactionGateway(*txn).loadOne(
            table::FeatureTransaction::featureId.equals(ridePhotos[0].id())
        );

        EXPECT_EQ(featureTransaction.transactionId(), transactionId);
    }
}

TEST_F(Fixture, test_update_published)
{
    auto photo = Feature{"sourceId",
                         Point2{44.01, 56.30},
                         Heading{10.0},
                         "2021-04-02 10:55:00+03",
                         mds::Key{"4510", "photo.jpg"},
                         Dataset::Agents}
                     .setSize({6, 9});
    EXPECT_FALSE(photo.shouldBePublished());
    photo.setAutomaticShouldBePublished(true);
    EXPECT_TRUE(photo.shouldBePublished());
    photo.setModeratorsShouldBePublished(false);
    EXPECT_FALSE(photo.shouldBePublished());
    photo.setModeratorsShouldBePublished(true);
    EXPECT_TRUE(photo.shouldBePublished());
    photo.setAutomaticShouldBePublished(false);
    EXPECT_TRUE(photo.shouldBePublished());  // moderator has priority
    photo.setDeletedByUser(true);
    EXPECT_FALSE(photo.shouldBePublished());
    photo.setDeletedByUser(false);  // purely theoretical
    EXPECT_TRUE(photo.shouldBePublished());
    photo.setDeletedFromMds(true);
    EXPECT_FALSE(photo.shouldBePublished());
    photo.setDeletedFromMds(false); // purely theoretical
    EXPECT_TRUE(photo.shouldBePublished());
    photo.resetPos();
    EXPECT_FALSE(photo.shouldBePublished());
}

TEST_F(Fixture, test_load_all_sources)
{
    const auto SRC1 = std::string{"src1"};
    const auto SRC2 = std::string{"src2"};
    auto features = Features{Feature{SRC1,
                                     Point2{44.01, 56.30},
                                     Heading{10.0},
                                     "2022-04-18 12:24:00+03",
                                     mds::Key{"4510", "photo1.jpg"},
                                     Dataset::Agents},
                             Feature{SRC2,
                                     Point2{44.01, 56.31},
                                     Heading{20.0},
                                     "2022-04-18 12:25:00+03",
                                     mds::Key{"4510", "photo2.jpg"},
                                     Dataset::Rides},
                             Feature{SRC1,
                                     Point2{44.02, 56.32},
                                     Heading{30.0},
                                     "2022-04-18 12:26:00+03",
                                     mds::Key{"4510", "photo3.jpg"},
                                     Dataset::Agents},
                             Feature{SRC2,
                                     Point2{44.03, 56.33},
                                     Heading{40.0},
                                     "2022-04-18 12:27:00+03",
                                     mds::Key{"4510", "photo4.jpg"},
                                     Dataset::Rides}};
    for (auto& feature : features) {
        feature.setSize({6, 9});
    }
    {
        auto txn = txnHandle();
        FeatureGateway{*txn}.insert(features);
        txn->commit();
    }

    auto sources = FeatureGateway{*txnHandle()}.loadAllSources();
    auto expected = std::vector{SRC1, SRC2};
    EXPECT_TRUE(std::is_permutation(
        sources.begin(), sources.end(), expected.begin(), expected.end()));
}

} // namespace maps::mrc::db::tests
