#include "fixture.h"

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/import_config_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/import_taxi_event_config_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/metadata_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/track_point_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/visibility.h>

#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/local_postgres/include/instance.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/multipolygon.h>
#include <maps/libs/geolib/include/polyline.h>
#include <maps/libs/geolib/include/serialization.h>
#include <maps/libs/geolib/include/spatial_relation.h>
#include <yandex/maps/geolib3/sproto.h>
#include <maps/libs/geolib/include/test_tools/comparison.h>
#include <yandex/maps/mrc/unittest/database_fixture.h>
#include <yandex/maps/mrc/unittest/log_disabling_fixture.h>
#include <yandex/maps/mrc/unittest/unittest_config.h>
#include <yandex/maps/wiki/common/date_time.h>
#include <yandex/maps/wiki/common/misc.h>

#include <chrono>
#include <future>
#include <mutex>
#include <set>
#include <tuple>
#include <utility>
#include <vector>

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

namespace maps::mrc::db::tests {
using namespace ::testing;

TEST_F(Fixture, test_metadata) {
    const std::string k1 = "key-to-integer";
    const std::string k2 = "key-to-double";
    const std::string k3 = " key to string ";
    const int v1 = 112233;
    const double v2 = 11.2233;
    const std::string v3 = " x y z ";
    {
        auto txn = txnHandle();
        MetadataGateway gtw{*txn};
        gtw.upsertByKey<int>(k1, v1);
        gtw.upsertByKey<double>(k2, v2);
        gtw.upsertByKey(k3, v3);
        txn->commit();
    }
    {
        auto txn = txnHandle();
        MetadataGateway gtw(*txn);
        EXPECT_EQ(v1, gtw.tryLoadByKey<int>(k1, 42));
        EXPECT_EQ(v2, gtw.tryLoadByKey<double>(k2, 42.42));
        EXPECT_EQ(v3, gtw.tryLoadByKey(k3));
        EXPECT_EQ(std::nullopt, gtw.tryLoadByKey("unknown"));
        EXPECT_EQ(3.14, gtw.tryLoadByKey<double>("pi", 3.14));
    }
}

TEST_F(Fixture, test_track_point) {
    using namespace std::chrono_literals;
    static const std::string SOURCE_1 = "source_1";
    static const std::string SOURCE_2 = "source_2";
    static const std::vector<geolib3::Point2> POINTS{{0, 0.5}, {0.5, 1}};
    static const auto TIME = chrono::parseIsoDateTime("2021-03-17T14:08:00+03");
    {
        auto txn = txnHandle();
        TrackPoints trackPoints;
        for (const auto& sourceId : {SOURCE_1, SOURCE_2}) {
            for (const auto& point : POINTS) {
                auto time = TIME + trackPoints.size() * 1s;
                trackPoints.emplace_back()
                    .setSourceId(sourceId)
                    .setGeodeticPos(point)
                    .setTimestamp(time);
            }
        }
        TrackPointGateway{*txn}.insert(trackPoints);
        for (const auto& trackPoint : trackPoints) {
            EXPECT_GT(trackPoint.id(), 0u);
        }
        txn->commit();
    }
    {
        auto txn = txnHandle();
        auto trackPoints = TrackPointGateway{*txn}.load(
            table::TrackPoint::sourceId.equals(SOURCE_1) &&
                table::TrackPoint::timestamp.greater(TIME)
        );
        EXPECT_EQ(trackPoints.size(), 1u);
        EXPECT_EQ(trackPoints.front().sourceId(), SOURCE_1);
    }
}

TEST_F(Fixture, test_track_point_is_augmented)
{
    db::TId gps = [&] {
        TrackPoint trackPoint{};
        trackPoint
            .setSourceId("source_1")
            .setGeodeticPos({0, 1})
            .setTimestamp(chrono::TimePoint::clock::now());
        auto txn = txnHandle();
        TrackPointGateway{*txn}.insert({trackPoint});
        txn->commit();
        return trackPoint.id();
    }();

    db::TId cartographer = [&] {
        TrackPoint trackPoint{};
        trackPoint.setSourceId("source_1")
            .setGeodeticPos({0, 2})
            .setTimestamp(chrono::TimePoint::clock::now())
            .setIsAugmented(true);
        auto txn = txnHandle();
        TrackPointGateway{*txn}.insert({trackPoint});
        txn->commit();
        return trackPoint.id();
    }();

    auto txn = txnHandle();
    auto trackPoints = TrackPointGateway{*txn}.load();
    EXPECT_EQ(trackPoints.size(), 2u);

    auto it = std::find_if(
        trackPoints.begin(), trackPoints.end(), [=](auto& trackPoint) {
            return trackPoint.id() == gps;
        });
    EXPECT_FALSE(it == trackPoints.end());
    EXPECT_FALSE(it->isAugmented());

    it = std::find_if(
        trackPoints.begin(), trackPoints.end(), [=](auto& trackPoint) {
            return trackPoint.id() == cartographer;
        });
    EXPECT_FALSE(it == trackPoints.end());
    EXPECT_TRUE(it->isAugmented());
}

TEST_F(Fixture, test_import_config)
{
    ImportConfigs importConfigs {
        { Dataset::TaxiSignalQ2, 225, 3, 30 },
        { Dataset::TaxiSignalQ2, 225, 4, 60 },
        { Dataset::NexarDashcams, 225, 3, 90 }
    };

    {
        auto txn = txnHandle();
        ImportConfigGateway{*txn}.insert(importConfigs);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        auto loadedImportConfigs = ImportConfigGateway{*txn}.load(
            table::ImportConfig::dataset.equals(Dataset::TaxiSignalQ2),
            sql_chemistry::orderBy(table::ImportConfig::fc));
        EXPECT_EQ(loadedImportConfigs.size(), 2u);
        EXPECT_EQ(loadedImportConfigs[0], importConfigs[0]);
        EXPECT_EQ(loadedImportConfigs[1], importConfigs[1]);
    }
}

TEST_F(Fixture, test_import_taxi_event_config)
{
    ImportTaxiEventConfigs importTaxiEventConfigs {
        { "high_acceleration", 5},
        { "mrc_capture", 10 }
    };

    {
        auto txn = txnHandle();
        ImportTaxiEventConfigGateway{*txn}.insert(importTaxiEventConfigs);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        auto gtw = ImportTaxiEventConfigGateway{*txn};
        auto loadedImportTaxiEventConfigs = gtw.load(
            table::ImportTaxiEventConfig::eventType.equals("high_acceleration"));
        EXPECT_EQ(loadedImportTaxiEventConfigs.size(), 1u);
        EXPECT_EQ(loadedImportTaxiEventConfigs[0], importTaxiEventConfigs[0]);
    }
}

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