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

#include <maps/wikimap/mapspro/services/mrc/libs/yt/include/io.h>
#include <maps/wikimap/mapspro/services/mrc/libs/yt/include/serialization.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/import_taxi/lib/video_events_feed.h>

#include <mapreduce/yt/tests/yt_unittest_lib/yt_unittest_lib.h>

using namespace ::testing;

using maps::introspection::operator==;
using maps::introspection::operator<<;

namespace maps::mrc::import_taxi::tests {

namespace {

const TString TABLES_PATH = "//tmp/import-taxi/";
const TString DEVICES_TABLE_PATH = TABLES_PATH + "devices";
const TString EVENTS_TABLE_PATH = TABLES_PATH + "events";
const std:: string EVENT_TYPE = "mrc_capture";

std::string SERIAL_NUM(int64_t id) { return "serial_number_" + std::to_string(id); }
std::string URL(int64_t id) { return "http://video" + std::to_string(id) + ".mp4"; }

chrono::TimePoint toTimePoint(int64_t seconds)
{
    return chrono::sinceEpochToTimePoint<std::chrono::seconds>(seconds);
}

const std::string EXTRA = R"(
{
    "LocationFilter": {
        "bad_location": false,
        "curvature": NaN,
        "history": {
            "timestamps": [
                100.0,
                101.0,
                102.0
            ],
            "values": [
                { "lat": 55.1, "lon": 37.1, "speed": 10.0 },
                { "lat": 55.2, "lon": 37.2, "speed": 10.0 },
                { "lat": 55.3, "lon": 37.3, "speed": 10.0 }
            ]
        }
    }
})";


struct YtDevice {
    int64_t id;
    std::string serialNumber;

    template<class T>
    static auto introspect(T& t) { return std::tie(t.id, t.serialNumber); }

    static constexpr auto columns() { return std::make_tuple("id", "serial_number"); }
};

struct YtVideoEvent {
    int64_t id;
    int64_t deviceId;
    double eventAt;
    std::string eventType;
    std::optional<std::string> videoUrl;
    std::optional<double> videoUrlExpiresAtSec;
    std::string extra;

    template<class T>
    static auto introspect(T& t)
    {
        return std::tie(
            t.id,
            t.deviceId,
            t.eventAt,
            t.eventType,
            t.videoUrl,
            t.videoUrlExpiresAtSec,
            t.extra);
    }

    static constexpr auto columns()
    {
        return std::make_tuple(
            "id",
            "device_id",
            "event_at",
            "event_type",
            "presigned_external_video_url",
            "presigned_external_video_url_expires_at",
            "extra");
    }

};

class EventsFeedFixture: public testing::Test {
public:
    EventsFeedFixture()
        : ytClient_(NYT::NTesting::CreateTestClient())
    {
        prepareDevicesData();
        prepareVideoEventsData();
    }

    NYT::IClientBasePtr ytClient() { return ytClient_; }

private:
    NYT::IClientBasePtr ytClient_;

    void prepareDevicesData()
    {
        if (!ytClient()->Exists(DEVICES_TABLE_PATH)) {
            ytClient()->Create(
                DEVICES_TABLE_PATH, NYT::NT_TABLE,
                NYT::TCreateOptions().Recursive(true));
        }

        std::vector<YtDevice> devices {
            {1, SERIAL_NUM(1)},
            {2, SERIAL_NUM(2)},
            {5, SERIAL_NUM(5)}
        };
        yt::saveToTable(*ytClient(), DEVICES_TABLE_PATH, devices);
    }

    void prepareVideoEventsData()
    {
        if (!ytClient()->Exists(EVENTS_TABLE_PATH)) {
            ytClient()->Create(
                EVENTS_TABLE_PATH, NYT::NT_TABLE,
                NYT::TCreateOptions().Recursive(true));
        }

        std::vector<YtVideoEvent> events {
            {1, 1, 1650001000, EVENT_TYPE, std::nullopt, std::nullopt, ""}, // No video url
            {2, 1, 1650002000, EVENT_TYPE, URL(2), 1500000000, ""},         // Video has expired
            {3, 1, 1650003000, EVENT_TYPE, URL(3), 2000000000, EXTRA},      // Ok
            {4, 7, 1650004000, EVENT_TYPE, URL(4), 2000000000, EXTRA},      // Ok, unknown device_id
            {5, 7, 1650005000, EVENT_TYPE, URL(5), 1500000000, ""},         // Video has expired
            {6, 2, 1650006000, EVENT_TYPE, URL(6), 2000000000, EXTRA}       // Ok
        };

        yt::saveToTable(
            *ytClient(),
            NYT::TRichYPath(EVENTS_TABLE_PATH).Schema(makeVideoEventsSchema()),
            events);
    }

    NYT::TTableSchema makeVideoEventsSchema()
    {
        using namespace NYT;
        TTableSchema schema;

        schema.AddColumn("id", EValueType::VT_INT64, ESortOrder::SO_ASCENDING);
        schema.AddColumn("device_id", EValueType::VT_INT64);
        schema.AddColumn("event_at", EValueType::VT_DOUBLE);
        schema.AddColumn("event_type", EValueType::VT_STRING);
        schema.AddColumn("presigned_external_video_url", EValueType::VT_STRING);
        schema.AddColumn("presigned_external_video_url_expires_at", EValueType::VT_DOUBLE);
        schema.AddColumn("extra", EValueType::VT_STRING);

        return schema;
    }
};

}  // namespace

TEST_F(EventsFeedFixture, test_reading_taxi_event)
{
    TaxiVideoEventsFeed eventsFeed(ytClient(), TABLES_PATH, 0);

    EXPECT_TRUE(eventsFeed.hasNext());
    auto event = eventsFeed.next();
    EXPECT_EQ(event.id, 3u);
    EXPECT_EQ(event.url, URL(3));
    EXPECT_EQ(event.sourceId, SERIAL_NUM(1));
    EXPECT_EQ(event.eventTime, toTimePoint(1650003000));
    EXPECT_TRUE(event.track.empty());
}

TEST_F(EventsFeedFixture, test_reading_taxi_events_skip_expired)
{
    TaxiVideoEventsFeed eventsFeed(ytClient(), TABLES_PATH, 0);

    EXPECT_TRUE(eventsFeed.hasNext());
    auto event = eventsFeed.next();
    EXPECT_EQ(event.id, 3u);

    EXPECT_TRUE(eventsFeed.hasNext());
    event = eventsFeed.next();
    EXPECT_EQ(event.id, 4u);
    EXPECT_EQ(event.sourceId, "taxi_signalq2_unknown_7");

    EXPECT_TRUE(eventsFeed.hasNext());
    event = eventsFeed.next();
    EXPECT_EQ(event.id, 6u);

    EXPECT_FALSE(eventsFeed.hasNext());
}


TEST_F(EventsFeedFixture, test_reading_taxi_events_start_from_id)
{
    TaxiVideoEventsFeed eventsFeed(ytClient(), TABLES_PATH, /*startId=*/5u);

    EXPECT_TRUE(eventsFeed.hasNext());
    auto event = eventsFeed.next();
    EXPECT_EQ(event.id, 6u);

    EXPECT_FALSE(eventsFeed.hasNext());

    EXPECT_EQ(eventsFeed.maxProcessedId(), 6u);
}

}  // namespace maps::mrc::import_taxi::tests
