#include "yt_ops.h"
#include "consts.h"

#include <mapreduce/yt/interface/client.h>
#include <maps/libs/common/include/exception.h>
#include <yandex/maps/wiki/common/yt.h>
#include <maps/libs/log8/include/log8.h>

#include <algorithm>
#include <unordered_set>

namespace bg = boost::gregorian;

/*                            Tables schemes
-------------------------------------------------------------------------------

// Matched tracks //
           ________ ________ ____________________ ________________
          |        |        |                    |                |
column :  |  clid  |  uuid  |  track_start_time  | persistent_id  | ... (irrelevant) ...
          |________|________|____________________|________________|
type:     |        |        |                    |                |
          | string | string |      string        |     uint64     |
          |________|________|____________________|________________|


// Roads stat //
               _______________ _______________
              |               |               |
column name:  | persistent_id | tracks_number |
              |_______________|_______________|
    type:     |               |               |
              |     uint64    |     uint64    |
              |_______________|_______________|

-------------------------------------------------------------------------------
*/

namespace maps::wiki::traffic_analyzer {

namespace {

const TString DAILY_TRAVEL_TIMES_DIR =
    "//home/maps/jams/production/data/travel_times";

const TString ROADS_STAT_TABLE = "//tmp/wiki_maps_oneway_hyps_roads_stat";

const TString PERSISTENT_ID = "persistent_id";
const TString CLID = "clid";
const TString UUID = "uuid";
const TString TRACK_START_TIME = "track_start_time";
const TString TRACKS_NUMBER = "tracks_number";
const TString TRACK_ID = "track_id";


class PersistentIdMapper : public
    NYT::IMapper<NYT::TTableReader<NYT::TNode>, NYT::TTableWriter<NYT::TNode>>
{
public:
    void Do(TReader* reader, TWriter* writer) override
    {
        for (; reader->IsValid(); reader->Next()) {
            const NYT::TNode& row = reader->GetRow();

            NYT::TNode res;
            res(PERSISTENT_ID, row[PERSISTENT_ID]);
            res(TRACK_ID, row[CLID].AsString() + "_" + row[UUID].AsString() +
                "_" + row[TRACK_START_TIME].AsString());
            writer->AddRow(res);
        }
    }
};

REGISTER_MAPPER(PersistentIdMapper);

class TrackNumberReducer : public
    NYT::IReducer<NYT::TTableReader<NYT::TNode>, NYT::TTableWriter<NYT::TNode>>
{
public:
    void Do(TReader* reader, TWriter* writer) override
    {
        uint64_t persistent_id = reader->GetRow()[PERSISTENT_ID].AsUint64();

        std::unordered_set<std::string> trackIds;
        for (; reader->IsValid(); reader->Next()) {
            const NYT::TNode& row = reader->GetRow();
            trackIds.insert(row[TRACK_ID].AsString());
        }

        NYT::TNode res;
        res(PERSISTENT_ID, persistent_id);
        res(TRACKS_NUMBER, trackIds.size());
        writer->AddRow(res);
    }
};

REGISTER_REDUCER(TrackNumberReducer);

/*
    Params:
    matchedTracksTables: YT matched tracks tables with info about roads and
        segments to which tracks were matched.
    roadsStatTable: YT road stat table with matched tracks number
        for each road in static road graph.
*/
void createYTRoadsStatTable(
        const std::vector<TString>& matchedTracksTables,
        const TString& roadsStatTable,
        const NYT::ITransactionPtr& clientTxn)
{
    for (const auto& matchedTable : matchedTracksTables) {
        REQUIRE(clientTxn->Exists(matchedTable),
            "Table " << matchedTable << " doesn't exists at YT");
    }

    clientTxn->Create(roadsStatTable, NYT::NT_TABLE,
        NYT::TCreateOptions().Recursive(true).Force(true));

    NYT::TMapReduceOperationSpec spec;

    for (const auto& matchedTable : matchedTracksTables) {
        spec.AddInput<NYT::TNode>(matchedTable);
    }

    spec.AddOutput<NYT::TNode>(TString(roadsStatTable));
    spec.SortBy({PERSISTENT_ID});
    spec.ReduceBy({PERSISTENT_ID});

    auto mapReduceOperation = clientTxn->MapReduce(
        spec,
        new PersistentIdMapper(),
        new TrackNumberReducer(),
        NYT::TOperationOptions().Wait(false));

    INFO() << "Operation, collecting roads matching statistics from matched tracks,"
           << "started. Operation id: " << GetGuidAsString(mapReduceOperation->GetId())
           << ". You can check its status at https://yt.yandex-team.ru/hahn";

    auto operationComplete = mapReduceOperation->Watch();
    operationComplete.Wait();
    operationComplete.GetValue();
}

/*
    Params:
    roadsStatTable: YT road stat table, with matched tracks number
        for each road in static road graph.
    Output:
        persistent_id of road -> number of matched tracks.
*/
MapPersIdToTracks
readYTRoadsStatTable(
    const TString& roadsStatTable,
    const NYT::ITransactionPtr& clientTxn)
{
    REQUIRE(clientTxn->Exists(roadsStatTable),
        "Table " << roadsStatTable << " doesn't exists at YT");

    MapPersIdToTracks roadIdToTracksNum;

    auto reader = clientTxn->CreateTableReader<NYT::TNode>(roadsStatTable);
    for (; reader->IsValid(); reader->Next()) {
        const NYT::TNode& row = reader->GetRow();

        auto persistent_id = row[PERSISTENT_ID].AsUint64();
        auto tracks_num = row[TRACKS_NUMBER].AsUint64();

        roadIdToTracksNum[persistent_id] = tracks_num;
    }

    return roadIdToTracksNum;
}

TString jamsMatchedTracksPath(bg::date signalsDay)
{
    return DAILY_TRAVEL_TIMES_DIR + "/" +
        bg::to_iso_extended_string(signalsDay);
}

std::vector<TString> jamsMatchedTracksPaths(bg::date_period signalsPeriod)
{
    std::vector<TString> res;
    for (bg::day_iterator day = signalsPeriod.begin();
         day != signalsPeriod.end(); ++day) {
        res.push_back(jamsMatchedTracksPath(*day));
    }
    return res;
}

} // namespace anonymous

PersIdToTracks getYTRoadsStat(bg::date_period signalsPeriod)
{
    auto client = common::yt::createYtClient(TString(YT_PROXY_NAME));
    auto clientTxn = client->StartTransaction();

    auto matchedTracksTables = jamsMatchedTracksPaths(signalsPeriod);

    createYTRoadsStatTable(matchedTracksTables, ROADS_STAT_TABLE, clientTxn);
    auto roadsStat = readYTRoadsStatTable(ROADS_STAT_TABLE, clientTxn);

    return PersIdToTracks(signalsPeriod, std::move(roadsStat));
}

std::string
oldestGraphVersionOfMatchedTracks(bg::date_period signalsPeriod)
{
    REQUIRE(!signalsPeriod.is_null(),
        "Invalid signals period " << signalsPeriod);

    auto client = common::yt::createYtClient(TString(YT_PROXY_NAME));
    auto clientTxn = client->StartTransaction();

    auto tables = jamsMatchedTracksPaths(signalsPeriod);

    std::for_each(tables.begin(), tables.end(), [&](const TString& table) {
        REQUIRE(clientTxn->Exists(table),
            "Table " << table << " doesn't exists at YT");
    });

    std::vector<std::string> versions;
    std::transform(tables.begin(), tables.end(), std::back_inserter(versions),
        [&](const TString& trackTable) {
            return clientTxn->Get(trackTable + "/@graph_version").AsString();
        }
    );
    return *std::min_element(versions.begin(), versions.end());
}

bg::date latestMatchedSignalsDay()
{
    auto client = common::yt::createYtClient(TString(YT_PROXY_NAME));

    std::vector<bg::date> matchedDays;
    for (const auto& day : client->List(DAILY_TRAVEL_TIMES_DIR)) {
        matchedDays.push_back(bg::from_string(day.AsString()));
    }

    REQUIRE(!matchedDays.empty(),
        "No matched days at " << DAILY_TRAVEL_TIMES_DIR);

    return *std::max_element(matchedDays.begin(), matchedDays.end());
}

} // namespace maps::wiki::traffic_analyzer
