#include "export_ymapsdf.h"
#include "dataset_maker.h"
#include "json2ymapsdf.h"
#include "measurable_task.h"
#include "pg_helpers.h"
#include "revision2json.h"

#include <maps/libs/common/include/file_utils.h>
#include <yandex/maps/wiki/tasks/ymapsdf.h>
#include <maps/libs/log8/include/log8.h>

#include <future>

#include <sys/types.h>
#include <sys/stat.h>

namespace maps::wiki::exporter {

ExportViaYmapsdf::ExportViaYmapsdf(
        const ExportConfig& exportCfg,
        const ExportFiles& exportFiles)
    : exportFiles_(exportFiles)
    , exportCfg_(exportCfg)
{}


void ExportViaYmapsdf::checkCanceled() const
{
    exportCfg_.checkCanceled();
}

bool ExportViaYmapsdf::processRegion(ResultFile dumpResultFile, const Region& region)
{
    const auto schemaName = exportCfg_.schema(region);
    const auto forRegion = (region == NO_REGION)
        ? ""
        : " for region '" + region + "'";

    if (!schemaExists(exportCfg().ymapsdfPool(), schemaName)) {
        exportCfg_.logger().logError()
            << "Failed to create ymapsdf" << forRegion << ". Skipping.";
        return false;
    }

    if (dumpResultFile == ResultFile::DUMP_GZ_TAR) {
        checkCanceled();
        runMeasurable(
            [&]{ ymapsdf2Dump(schemaName); },
            exportCfg_.logger(), "generating dump" + forRegion);

        checkCanceled();
        runMeasurable(
            [&]{ gzTar(
                    exportFiles_(TmpFile::DUMP_DIR),
                    exportFiles_(ResultFile::DUMP_GZ_TAR, region)); },
            exportCfg_.logger(), "archiving (gz+tar) dump" + forRegion);
    } else {
        ASSERT(dumpResultFile == ResultFile::DUMP_TAR_GZ);

        checkCanceled();
        runMeasurable(
            [&]{ ymapsdf2Dump(schemaName); },
            exportCfg_.logger(), "generating dump" + forRegion);

        checkCanceled();
        runMeasurable(
            [&]{ tarGz(
                    exportFiles_(TmpFile::DUMP_DIR),
                    exportFiles_(ResultFile::DUMP_TAR_GZ, region)); },
            exportCfg_.logger(), "archiving (tar+gz) dump" + forRegion);
    }

    auto objectIdsDumpFilepath = exportFiles_(TmpFile::OBJECT_IDS_DUMP_FILE);
    if (fs::exists(objectIdsDumpFilepath)) {
        checkCanceled();
        runMeasurable(
            [&]{ gzipFile(
                    objectIdsDumpFilepath,
                    exportFiles_(ResultFile::OBJECT_IDS_ARCHIVE, region)); },
            exportCfg_.logger(), "archiving object ids" + forRegion);
    }

    auto json2ymapsdfLogFilepath = exportFiles_(TmpFile::JSON2YMAPSDF_LOG);
    if (fs::exists(json2ymapsdfLogFilepath)) {
        checkCanceled();
        runMeasurable(
            [&]{ copySingleFile(
                    json2ymapsdfLogFilepath,
                    exportFiles_(ResultFile::JSON2YMAPSDF_LOG, region)); },
            exportCfg_.logger(), "copy error log file" + forRegion);
    }

    /*
     * WARN: at the time, there is no way to specify per-region dump folder.
     * In order to allow further regions to be dumped, we have to emulate this behavior manually.
     */
    fs::rename(
        exportFiles_(TmpFile::DUMP_DIR),
        exportFiles_(TmpFile::DUMP_DIR) + region
    );
    fs::create_directory(exportFiles_(TmpFile::DUMP_DIR));
    return true;
}

Datasets ExportViaYmapsdf::run(ResultFile dumpResultFile)
{
    json2ymapsdfLoadConfig(exportCfg_, exportFiles_);

    auto mkFifoRes = mkfifo(exportFiles_(TmpFile::JSON_LIST_FILE).c_str(), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    REQUIRE(
        mkFifoRes == 0,
        "Failed to create fifo: " << exportFiles_(TmpFile::JSON_LIST_FILE) << ". " << strerror(errno));

    const auto json2YmapsdfTask = MeasurableTask(
        [&]{ json2ymapsdf(exportCfg_, exportFiles_); },
        exportCfg_.logger(), "load json");

    checkCanceled();
    auto json2YmapsdfFuture = std::async(std::launch::async, json2YmapsdfTask);
    revision2Json(exportCfg_, exportFiles_);
    checkCanceled();
    json2YmapsdfFuture.get();

    if (exportCfg_.keepJson()) {
        checkCanceled();
        runMeasurable(
            [&]{ tarGz(exportFiles_(TmpFile::JSON_DIR), exportFiles_.jsonBackupFile()); },
            exportCfg_.logger(), "archiving json");
    }

    checkCanceled();
    runMeasurable(
        [&]{ fs::remove_all(fs::path{ exportFiles_(TmpFile::JSON_DIR) }); },
        exportCfg_.logger(), "remove tmp json dir");

    runMeasurable(
        [&] {
            tar(tasks::ymapsdf::getGardenSchemasDirectory(),
                exportFiles_.ymapsdfSchemasArchive());
        },
        exportCfg_.logger(),
        "archiving ymapsdf schemas"
    );

    DatasetMaker datasetMaker(exportCfg_, exportFiles_);

    for (const auto& region: exportCfg_.regions()) {
        try {
            if (processRegion(dumpResultFile, region)) {
                checkCanceled();
                datasetMaker.make(region);
            }
        } catch (...) {
            datasetMaker.cancel();
            throw;
        }
    }

    fs::remove(exportFiles_(TmpFile::JSON2YMAPSDF_LOG));

    return datasetMaker.datasets();
}

} // namespace maps::wiki::exporter
