#include "metadata.h"
#include "nexar_import_tile_update_info.h"
#include "pqxx/transaction_base.hxx"

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/for_each_batch.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/metadata_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/nexar_import_tile_update_info_gateway.h>

#include <maps/libs/chrono/include/time_point.h>
#include <string>
#include <utility>

namespace maps::mrc::import_nexar {

namespace {

const std::string METADATA_PREFIX = "import_nexar.";
const std::string LAST_RUN_TIME = METADATA_PREFIX + "last_run_time";


template<class Container>
void save(pqxx::transaction_base& txn, Container&& infos)
{
    db::NexarImportTileUpdateInfoGateway gtw(txn);
    common::forEachBatch(std::forward<Container>(infos), 1000,
        [&](auto begin, auto end){
            db::NexarImportTileUpdateInfoGateway::EntitiesRef entitiesRef(begin, end);
            gtw.upsert(entitiesRef);
        });
}

TileImportParams makeTileImportParams(const db::NexarImportTileUpdateInfo& info)
{
    return {info.geoId(), info.fc(), info.x(), info.y(), info.z()};
}

} // namespace

Metadata::Metadata(std::optional<chrono::TimePoint> lastSuccessRunTime,
            db::NexarImportTileUpdateInfos tileUpdateInfos)
    : lastSuccessRunTime_(std::move(lastSuccessRunTime))
{
    for (auto&& info : std::move(tileUpdateInfos)) {
        auto key = makeTileImportParams(info);
        tileParamToInfo_.emplace(std::move(key), std::move(info));
    }
}

db::NexarImportTileUpdateInfo Metadata::getTileUpdateInfo(const TileImportParams& p) const
{
    auto it = tileParamToInfo_.find(p);
    if (it == tileParamToInfo_.end()) {
        return {p.geoId, p.fc, p.x, p.y, p.z};
    }
    return it->second;
}

void Metadata::setTileUpdateInfo(db::NexarImportTileUpdateInfo info)
{
    auto key = makeTileImportParams(info);
    auto it = tileParamToInfo_.find(key);
    if (it == tileParamToInfo_.end()) {
        tileParamToInfo_.emplace(std::move(key), std::move(info));
    } else {
        it->second = std::move(info);
    }
}

db::NexarImportTileUpdateInfos Metadata::tileUpdateInfos() const
{
    db::NexarImportTileUpdateInfos result;
    result.reserve(tileParamToInfo_.size());
    for(const auto& [key, value] : tileParamToInfo_) {
        result.push_back(value);
    }
    return result;
}

Metadata loadMetadata(pqxx::transaction_base& txn)
{
    std::optional<chrono::TimePoint> lastSuccessRunTime;

    auto values = db::MetadataGateway(txn).load(
        db::table::Metadata::key.like(METADATA_PREFIX + "%"));

    for (const auto& value: values) {
        if (value.key() == LAST_RUN_TIME) {
            lastSuccessRunTime = chrono::parseSqlDateTime(value.value());
        }
    }
    auto tileUpdateInfos = db::NexarImportTileUpdateInfoGateway(txn).load();
    return {std::move(lastSuccessRunTime), std::move(tileUpdateInfos)};
}

void saveMetadata(pqxx::transaction_base& txn, Metadata& metadata)
{
    std::vector<db::Metadata> dbMetadata;
    if (metadata.lastSuccessRunTime().has_value()) {
        dbMetadata.emplace_back(
            LAST_RUN_TIME,
            chrono::formatSqlDateTime(metadata.lastSuccessRunTime().value()));
    }
    db::MetadataGateway(txn).upsert(dbMetadata);
    save(txn, metadata.tileUpdateInfos());
}

} // namespace maps::mrc::import_nexar
