#include "run.h"

#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/ymapsdf/schema/constraints.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/helpers.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/config.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/common/data_error.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/transformers/configuration.h>
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/transformers/serialize.h>
#include "finalize/fix_ymapsdf.h"
#include "isocode/builder.h"
#include "isocode/region_builder.h"
#include "transform/transform.h"

#include <maps/libs/json/include/prettify.h>
#include <maps/libs/pgpool/include/pgpool3.h>

#include <yandex/maps/wiki/threadutils/threadpool.h>

#include <mutex>
#include <fstream>

using namespace std::chrono_literals;

namespace maps::wiki::json2ymapsdf {

namespace {

pgpool3::PoolConstants
poolConstants(const Params& params)
{
    auto maxConnections = params.threadCount + 5; // up minimum
    auto minConnections = maxConnections / 2 + 1;

    pgpool3::PoolConstants constants(
        minConnections, // masterSize
        maxConnections, // masterMaxSize
        1,              // slaveSize
        1);             // slaveMaxSize

    constants.getTimeoutMs = 20s;
    constants.pingIntervalMs = 5s;
    constants.pingTimeoutMs = 60s;
    constants.timeoutEarlyOnMasterUnavailable = false;

    return constants;
}

std::string
createAndValidateRegionSchema(
    const Params& params,
    const ymapsdf::schema::Constraints& constraints,
    const Region& region,
    size_t threads)
{
    WARN() << "CREATING VIEW FOR REGION " << region.id;
    try {
        auto regionSchemaName = ymapsdf::schema::createRegionView(params, region);
        constraints.validateUniqueIndexes(regionSchemaName, threads);
        if (params.overallValidation) {
            constraints.validateIntegrityChecks(regionSchemaName, threads);
        }
        constraints.validateSql(regionSchemaName, threads);
        return regionSchemaName;
    } catch (...) {
        DATA_ERROR() << "Creating " << region.id << " dataset failed";
        PROGRESS() << "Creating " << region.id << " dataset failed";
    }
    return {};
}

std::vector<std::string>
createAndValidateRegionSchemas(
    const Params& params,
    const ymapsdf::schema::Constraints& constraints,
    Regions& regions)
{
    const auto THREADS = 2;
    ThreadPool threadPool(THREADS);

    std::mutex mutex;
    std::vector<std::string> regionSchemas;

    const auto workThreads = params.threadCount / THREADS;
    for (auto& region: regions) {
        if (region.status == Region::Status::Skip) {
            WARN() << "SKIP VIEW FOR REGION " << region.id;
            continue;
        }

        threadPool.push([&, regionPtr = &region] {
            auto regionSchemaName = createAndValidateRegionSchema(
                params, constraints, *regionPtr, workThreads);

            if (!regionSchemaName.empty()) {
                std::lock_guard<std::mutex> lock(mutex);
                regionSchemas.push_back(regionSchemaName);
            } else {
                regionPtr->status = Region::Status::Failed;
            }
        });
    }
    threadPool.shutdown();

    return regionSchemas;
}

} // namespace

void
tds2ymapsdf(const Params& params)
{
    pgpool3::Pool pool(params.connStr, poolConstants(params));

    const auto schema = ymapsdf::schema::createSchema(params);
    const auto constraints = ymapsdf::schema::loadConstraints(pool, params);

    transformers::configure(*schema, params);

    loadAndTransformToYmapsdf(pool, *schema, params);

    constraints.createIndexes(schema->name(), params.threadCount);

    if (!params.split) {
        fixYmapsdf(pool, schema->name());
        constraints.validateUniqueIndexes(schema->name(), params.threadCount);
        constraints.validateIntegrityChecks(schema->name(), params.threadCount);
        constraints.validateSql(schema->name(), params.threadCount);
        return;
    }

    isocode::build(pool, *schema, params);
    auto regions = isocode::loadRegions(pool, schema->name(), params.regionIds);
    fixYmapsdf(pool, schema->name());

    auto regionSchemas =
        createAndValidateRegionSchemas(params, constraints, regions);

    DATA_ERROR() << "Export finished";

    DATA_REQUIRE(!regionSchemas.empty(), "All regional datasets have errors");

    isocode::updateRegionsStatus(pool, regionSchemas, regions);
}

void
printConfiguration(const Params& params)
{
    auto schema = ymapsdf::schema::createSchema(params);

    transformers::configure(*schema, params);
    std::ofstream ofs(params.printConfigurationFile);
    std::stringstream ss;
    transformers::serializeJson(ss, *schema);
    json::prettifyJson(ss, ofs, 2, 100, true);
}

} // namespace maps::wiki::json2ymapsdf
