#include <maps/wikimap/mapspro/services/mrc/long_tasks/drive_activity_stat/lib/process_yt_tables.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/drive_activity_stat/lib/metadata.h>
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/yt/include/common.h>
#include <maps/wikimap/mapspro/services/mrc/libs/yt/include/io.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/pgpool3utils/include/yandex/maps/pgpool3utils/pg_advisory_mutex.h>

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/pg_locks.h>
#include <mapreduce/yt/interface/init.h>

namespace {

/// @return new last processed table
std::optional<std::string>
recalculateFirmwareUpdaterLogs(
    NYT::IClientBase& client,
    const std::string& firmwareUpdaterHost,
    const std::optional<std::string>& lastProcessedTable,
    const std::string& ytLogsPath,
    const std::string& outputTablePath)
{
    INFO() << "Processing firmware upodater logs";
    auto tablesToProcess = maps::mrc::drive_activity_stat::evalTablesToProcess(
        client, ytLogsPath, lastProcessedTable);
    INFO() << "There are " << tablesToProcess.size() << " tables to process";
    if (tablesToProcess.empty()) {
        return lastProcessedTable;
    }

    const std::string tmpOutputTable = outputTablePath + "-tmp";

    INFO() << "Calculating activity in YT to " << tmpOutputTable;
    maps::mrc::drive_activity_stat::calculateCameraActivity(
        client,
        firmwareUpdaterHost,
        ytLogsPath,
        tablesToProcess,
        tmpOutputTable,
        maps::mrc::yt::PoolType::Processing
    );

    if (!client.Exists(TString(outputTablePath))) {
        client.Move(TString(tmpOutputTable), TString(outputTablePath));
    } else {
        INFO() << "Merging data from " << tmpOutputTable << " to " << outputTablePath;
        maps::mrc::drive_activity_stat::mergeCameraActivityTables(
            client,
            tmpOutputTable,
            outputTablePath,
            maps::mrc::yt::PoolType::Processing);
        client.Remove(TString(tmpOutputTable));
    }
    return tablesToProcess.back();
}


/// @return new last processed table
std::optional<std::string>
recalculateCarsharingActivityLogs(
    NYT::IClientBase& client,
    const std::string& ytDevicesRegistryPath,
    const std::optional<std::string>& lastProcessedTable,
    const std::string& ytLogsPath,
    const std::string& outputTablePath)
{
    INFO() << "Processing carsharing telematica logs";

    if (!client.Exists(TString(ytDevicesRegistryPath))) {
        WARN() << "Devices registry table " << ytDevicesRegistryPath
            << " does not exist";
        return lastProcessedTable;
    }

    auto devicesRegistry =
        maps::mrc::yt::loadFromTable<std::vector<maps::mrc::drive_activity_stat::DevicesRegistryRecord>>(
            client, TString(ytDevicesRegistryPath));

    auto tablesToProcess = maps::mrc::drive_activity_stat::evalTablesToProcess(
        client, ytLogsPath, lastProcessedTable);
    INFO() << "There are " << tablesToProcess.size() << " tables to process";
    if (tablesToProcess.empty()) {
        return lastProcessedTable;
    }

    const std::string tmpOutputTable = outputTablePath + "-tmp";

    INFO() << "Calculating activity in YT to " << tmpOutputTable;
    maps::mrc::drive_activity_stat::calculateCarsharingTelematicsActivity(
        client,
        devicesRegistry,
        ytLogsPath,
        tablesToProcess,
        tmpOutputTable,
        maps::mrc::yt::PoolType::Processing
    );

    if (!client.Exists(TString(outputTablePath))) {
        client.Move(TString(tmpOutputTable), TString(outputTablePath));
    } else {
        INFO() << "Merging data from " << tmpOutputTable << " to " << outputTablePath;
        maps::mrc::drive_activity_stat::mergeCarsharingTelematicsActivityTables(
            client,
            tmpOutputTable,
            outputTablePath,
            maps::mrc::yt::PoolType::Processing);
        client.Remove(TString(tmpOutputTable));
    }
    return tablesToProcess.back();
}

} // namespace

int main(int argc, const char** argv) try {
    NYT::Initialize(argc, argv);
    using namespace maps;
    log8::setLevel(log8::Level::INFO);
    maps::cmdline::Parser parser;

    auto configPath = parser.string("config")
            .help("path to mrc config");

    auto secretVersion = parser.string("secret-version")
            .help("version for secrets from yav.yandex-team.ru");

    auto syslog = parser.string("syslog-tag")
            .help("redirect log output to syslog with given tag");

    auto ytServiceLogsPath = parser.string("service-logs-path")
            .defaultValue("//home/logfeller/logs/maps-log/1d");

    auto ytCarsharingLogsPath = parser.string("carsharing-logs-path")
            .defaultValue("//home/logfeller/logs/carsharing-telematics-events-log/1d");

    auto outputCameraYtTableName = parser.string("output-camera-table-name")
            .defaultValue("drive_activity");

    auto outputCarsharingYtTableName = parser.string("output-carsharing-table-name")
            .defaultValue("drive_telematics_activity");

    auto devicesRegistryYtTableName = parser.string("input-devices-registry-table-name")
            .defaultValue("drive_devices_registry");

    auto lastFirmwareUpdaterProcessedTable =
        parser.string("last-firmware-updater-processed-table");

    auto lastCarsharingTelematicsProcessedTable =
        parser.string("last-carsharing-telematics-processed-table");

    parser.parse(argc, const_cast<char**>(argv));

    if (syslog.defined()) {
        maps::log8::setBackend(maps::log8::toSyslog(syslog));
    }

    INFO() << "Starting";
    const auto config =
        maps::mrc::common::templateConfigFromCmdPath(secretVersion, configPath);

    maps::wiki::common::PoolHolder mrcPoolHolder =
        config.makePoolHolder(maps::mrc::common::LONG_READ_DB_ID,
                              maps::mrc::common::LONG_READ_POOL_ID);
    maps::pgp3utils::PgAdvisoryXactMutex mutex(mrcPoolHolder.pool(),
        static_cast<int64_t>(maps::mrc::common::LockId::CalculateDriveActivity));
    if (!mutex.try_lock()) {
        INFO() << "Another process is ongoing";
        return EXIT_SUCCESS;
    }

    auto metadata = maps::mrc::drive_activity_stat::loadMetadata(mutex.writableTxn());

    auto ytClient = config.externals().yt().makeClient();
    auto ytTxn = ytClient->StartTransaction();
    const std::string outputCameraActivityTablePath =
        config.externals().yt().path() + "/" + outputCameraYtTableName;
    INFO() << "Output camera activity table path: "
           << outputCameraActivityTablePath;

    metadata.lastFirmwareUpdaterProcessedTable =
        recalculateFirmwareUpdaterLogs(
            *ytTxn,
            config.externals().firmwareUpdaterHost(),
            lastFirmwareUpdaterProcessedTable.defined()
                ? lastFirmwareUpdaterProcessedTable
                : metadata.lastFirmwareUpdaterProcessedTable,
            ytServiceLogsPath,
            outputCameraActivityTablePath);

    const std::string outputCarsharingActivityTablePath =
        config.externals().yt().path() + "/" + outputCarsharingYtTableName;
    INFO() << "Output carsharing activity table path: "
           << outputCarsharingActivityTablePath;
    const std::string devicesRegistryTablePath =
        config.externals().yt().path() + "/" + devicesRegistryYtTableName;

    metadata.lastCarsharingTelematicsProcessedTable =
        recalculateCarsharingActivityLogs(
            *ytTxn,
            devicesRegistryTablePath,
            lastCarsharingTelematicsProcessedTable.defined()
                ? lastCarsharingTelematicsProcessedTable
                : metadata.lastCarsharingTelematicsProcessedTable,
            ytCarsharingLogsPath,
            outputCarsharingActivityTablePath);

    ytTxn->Commit();
    metadata.lastSuccessRunTime = maps::chrono::TimePoint::clock::now();
    maps::mrc::drive_activity_stat::saveMetadata(mutex.writableTxn(), metadata);
    mutex.writableTxn().commit();
    INFO() << "Done";

    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    FATAL() << e;
    return EXIT_FAILURE;
} catch (const yexception& e) {
    FATAL() << e.what();

    if (e.BackTrace()) {
        FATAL() << e.BackTrace()->PrintToString();
    }

    return EXIT_FAILURE;
} catch (const std::exception& e) {
    FATAL() << e.what();
    return EXIT_FAILURE;
} catch (...) {
    FATAL() << "Caught unknown exception";
    return EXIT_FAILURE;
}
