#include "cleaner.h"

#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>

#include <chrono>

namespace ds = maps::wiki::mds_dataset;

namespace maps {
namespace wiki {
namespace exporter {
namespace cleaner {
namespace {

using Reader = ds::DatasetReader<ds::ExportMetadata>;
using Writer = ds::DatasetWriter<ds::ExportMetadata>;
using Dataset = ds::Dataset<ds::ExportMetadata>;
using Datasets = std::vector<Dataset>;

Datasets selectForDeletion(
    const Datasets& datasets,
    size_t numTotalToKeep,
    size_t numTestedToKeep)
{
    if (datasets.size() <= numTotalToKeep) {
        return {};
    }

    ASSERT(numTotalToKeep >= numTestedToKeep);

    Datasets result;
    size_t keeped = 0;

    result.reserve(datasets.size() - numTotalToKeep);
    std::remove_copy_if(
        datasets.begin(), datasets.end(),
        std::back_inserter(result),
        [&keeped, numTestedToKeep](const Dataset& dataset) {
            if (keeped < numTestedToKeep && dataset.metadata().tested() == ds::IsTested::Yes) {
                ++keeped;
                return true;
            }
            return false;
        }
    );

    result.erase(
        result.begin(),
        result.begin() + numTotalToKeep - keeped);

    return result;
}


void deleteDatasets(
    pgpool3::Pool& pool,
    mds::Mds& mds,
    const Datasets& datasets)
{
    Writer writer(mds, pool);
    for (const auto& dataset: datasets) {
        INFO() << "Deleting dataset id = " << dataset.id()
            << ", region = '" << dataset.region() << "'";
        try {
            writer.deleteDataset(dataset.id(), dataset.region());
        } catch (const std::exception& e) {
            WARN()
                << "Can't delete dataset id = " << dataset.id()
                << ", region = '" << dataset.region() << "'"
                << ": " << e.what();
        }
    }
}

} // namespace

void deleteObsoleteDatasets(
    pgpool3::Pool& pool,
    mds::Mds& mds,
    ds::Subset subset,
    size_t maxAgeDays)
{
    const auto timestamp = std::chrono::system_clock::now() - std::chrono::hours(maxAgeDays*24);

    const auto obsoleteDatasets = [&]() {
        auto txn = pool.masterReadOnlyTransaction();
        Reader::FilterType filter(*txn);
        filter.bySubset(subset).createdBefore(timestamp);
        return Reader::datasets(*txn, filter);
    };

    deleteDatasets(pool, mds, obsoleteDatasets());
}

void deleteRedundantDatasets(
    pgpool3::Pool& pool,
    mds::Mds& mds,
    ds::DatasetStatus status,
    ds::Subset subset,
    ds::Region region,
    size_t numTotalToKeep,
    size_t numTestedToKeep)
{
    INFO()
        << "Cleaning datasets with status=" << status << " "
        << "from subset=" << subset << " "
        << "for region='" << region << "'. "
        << "Keep " << numTotalToKeep << " datasets where at least (if possible) "
        << numTestedToKeep << " tested datasets.";

    const auto allDatasets = [&]() {
        auto txn = pool.masterReadOnlyTransaction();
        Reader::FilterType filter(*txn);
        filter.bySubset(subset).byStatus(status).byRegion(region);
        // Returned datasets are sorted by timestamp starting from newest
        return Reader::datasets(*txn, filter);
    };

    const auto redundantDatasets = selectForDeletion(allDatasets(), numTotalToKeep, numTestedToKeep);
    deleteDatasets(pool, mds, redundantDatasets);
}

std::set<ds::Region> loadAllRegions(
    pgpool3::Pool& pool,
    ds::Subset subset)
{
    const auto allDatasets = [&]() {
        auto txn = pool.masterReadOnlyTransaction();
        Reader::FilterType filter(*txn);
        filter.bySubset(subset);
        return Reader::datasets(*txn, filter);
    };

    std::set<ds::Region> regions;
    for (const auto& dataset: allDatasets()) {
        regions.insert(dataset.region());
    }

    return regions;
}

} // cleaner
} // exporter
} // wiki
} // maps
