#include "video_events_feed.h"
#include "common.h"

#include <maps/libs/common/include/exception.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/log8/include/log8.h>

#include <string_view>

using namespace NYT;

namespace maps::mrc::import_taxi {
namespace tbl {

const std::string EVENTS_TABLE = "events";
const std::string DEVICES_TABLE = "devices";

} // namespace tbl

namespace col {

const TString DEVICE_ID = "device_id";
const TString EVENT_AT = "event_at";
const TString EVENT_TYPE = "event_type";
const TString EXTRA = "extra";
const TString ID = "id";
const TString SERIAL_NUMBER = "serial_number";
const TString VIDEO_URL = "presigned_external_video_url";
const TString VIDEO_URL_EXPIRES_AT = "presigned_external_video_url_expires_at";

} // namespace col

namespace {

const std::string UNKNOWN_SOURCE_ID = "taxi_signalq2_unknown_";

} // namespace


TaxiVideoEventsFeed::TaxiVideoEventsFeed(
    NYT::IClientBasePtr ytClient,
    const std::string& tablesDir,
    db::TId maxProcessedId
)
    : VideoEventsFeed(maxProcessedId)
    , eventsTablePath_(tablesDir + tbl::EVENTS_TABLE)
    , devicesTablePath_(tablesDir + tbl::DEVICES_TABLE)
    , ytClient_(std::move(ytClient))
{
    readDeviceIdMap();

    auto fromId = maxProcessedId + 1;
    auto toId = std::numeric_limits<int64_t>::max;

    INFO() << "Create video events reader from table " << eventsTablePath_
           << ". Start reading from id = " << fromId;

    eventsReader_ = ytClient_->CreateTableReader<TNode>(
        TRichYPath(eventsTablePath_)
            .AddRange(TReadRange::FromKeys(fromId, toId))
            .Columns({
                col::ID,
                col::DEVICE_ID,
                col::EVENT_AT,
                col::EVENT_TYPE,
                col::EXTRA,
                col::VIDEO_URL,
                col::VIDEO_URL_EXPIRES_AT}));

    moveToNextUsefulRow();
    INFO() << "TaxiVideoEventsFeed constructed";
}

bool TaxiVideoEventsFeed::hasNext()
{
    return eventsReader_->IsValid();
}

VideoEvent TaxiVideoEventsFeed::next()
{
    REQUIRE(eventsReader_->IsValid(), "Video events feed finished unexpectedly");
    const auto& row = eventsReader_->GetRow();

    auto id = row[col::ID].AsInt64();
    INFO() << "Reading video event " << id;

    auto sourceId = getSourceId(row[col::DEVICE_ID].AsInt64());
    auto eventTime = toTimePoint(row[col::EVENT_AT].AsDouble());
    auto eventType = row[col::EVENT_TYPE].AsString();
    auto extra = row[col::EXTRA].AsString();
    auto videoUrl = row[col::VIDEO_URL].AsString();

    auto videoEvent = VideoEvent{
        .id = id,
        .eventType = eventType,
        .url = videoUrl,
        .sourceId = sourceId,
        .eventTime = eventTime,
        .track = {} // track will be filled later
    };

    maxProcessedId_ = id;
    eventsReader_->Next();
    moveToNextUsefulRow();

    return videoEvent;
}

void TaxiVideoEventsFeed::readDeviceIdMap()
{
    INFO() << "Reading deviceId to serialNumber map";

    TTableReaderPtr<TNode> reader(ytClient_->CreateTableReader<TNode>(devicesTablePath_));

    for (; reader->IsValid(); reader->Next()) {
        const TNode& row = reader->GetRow();
        auto rowId = row[col::ID].AsInt64();
        auto serialNumber = row[col::SERIAL_NUMBER].AsString();
        deviceIdMap_.emplace(rowId, std::move(serialNumber));
    }
    INFO() << "Done reading deviceId to serialNumber map, size = " << deviceIdMap_.size();
}

std::string TaxiVideoEventsFeed::getSourceId(uint64_t deviceId) const
{
    auto it = deviceIdMap_.find(deviceId);
    if (it != deviceIdMap_.end()) {
        return it->second;
    } else {
        WARN() << "Could not find serial number of device " << deviceId;
        return UNKNOWN_SOURCE_ID + std::to_string(deviceId);
    }
}

void TaxiVideoEventsFeed::moveToNextUsefulRow()
{
    INFO() << "Looking for the next useful row";

    size_t counter = 0;
    for (; eventsReader_->IsValid(); eventsReader_->Next()) {
        const auto& row = eventsReader_->GetRow();
        const auto id = row[col::ID].AsInt64();
        const auto& url = row[col::VIDEO_URL];
        const auto& expiresAt = row[col::VIDEO_URL_EXPIRES_AT];
        const auto& extra = row[col::EXTRA];

        if (++counter % 1000 == 0) {
            INFO() << "Skipped " << counter << " rows. Current id = " << id;
        }

        if (url.HasValue() && expiresAt.HasValue() && extra.HasValue() &&
                chrono::TimePoint::clock::now() < toTimePoint(expiresAt.AsDouble())) {
            INFO() << "Next useful event id = " << id;
            return;
        }
        maxProcessedId_ = id;
    }
}

} // namespace maps::mrc::import_taxi
