#include "blur.h"
#include "process.h"
#include "utility.h"

#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/feature_gateway.h>

#include <mutex>

namespace maps::mrc::blur_mds {
namespace {

constexpr auto ThreadsNumber = 16;
constexpr auto BatchSize = 100;

template <class Function>
void invokeNoexcept(Function fn, std::string_view onExceptionMessage) noexcept
try {
    std::invoke(fn);
}
catch (const std::exception& e) {
    WARN() << concat("skip ", onExceptionMessage, " ", e.what());
}
catch (...) {
    WARN() << concat("skip ", onExceptionMessage);
}

void deleteFromMds(mds::Mds& mds, const std::vector<mds::Key>& keys) noexcept
{
    for (const auto& key : keys) {
        invokeNoexcept([&] { mds.del(key); },
                       concat("mds key ", key.groupId, " ", key.path));
    }
}

bool blurrable(const auto& feature,
               const ObjectsByFeatureId& objectsByFeatureId)
{
    return (objectsByFeatureId.contains(feature.id()) ||
            feature.dataset() == db::Dataset::TaxiSignalQ2) &&
           feature.mdsKey().path.find(BlurredSuffix) == std::string::npos;
}

}  // namespace

size_t updateFeatures(pgpool3::Pool& pool,
                      mds::Mds& mds,
                      std::span<db::Feature> features,
                      const ObjectsByFeatureId& objectsByFeatureId,
                      bool dryRun) noexcept
{
    static const auto NoObjects = db::ObjectsInPhoto{};
    auto newKeys = std::vector<mds::Key>{};
    auto oldKeys = std::vector<mds::Key>{};
    auto committed = false;
    newKeys.reserve(std::size(features));
    oldKeys.reserve(std::size(features));
    for (auto& feature : features) {
        if (!blurrable(feature, objectsByFeatureId)) {
            continue;
        }
        invokeNoexcept(
            [&] {
                auto image = mds.get(feature.mdsKey());
                auto it = objectsByFeatureId.find(feature.id());
                image = blurImage(
                    image,
                    feature,
                    it == objectsByFeatureId.end() ? NoObjects : it->second);
                if (!dryRun) {
                    auto path =
                        concat(feature.mdsKey().path, "/", BlurredSuffix);
                    auto newKey = mds.post(path, image).key();
                    auto oldKey = feature.mdsKey();
                    newKeys.push_back(newKey);
                    feature.setMdsKey(newKey);
                    oldKeys.push_back(oldKey);
                }
            },
            concat("feature id ", feature.id()));
    }
    if (!dryRun) {
        invokeNoexcept(
            [&] {
                auto txn = pool.masterWriteableTransaction();
                db::FeatureGateway(*txn).update(features,
                                                db::UpdateFeatureTxn::Yes);
                txn->commit();
                committed = true;
            },
            concat("features number ", std::size(features)));
    }
    deleteFromMds(mds, committed ? oldKeys : newKeys);
    return committed ? newKeys.size() : 0;
}

void process(pgpool3::Pool& pool,
             mds::Mds& mds,
             const geolib3::MultiPolygon2& mercatorGeom,
             bool dryRun)
{
    auto features = loadFeatures(pool, mercatorGeom);
    auto objectsByFeatureId =
        loadObjectsInPhoto(pool, invokeForEach(features, &db::Feature::id));
    std::erase_if(features, [&](const auto& feature) {
        return !blurrable(feature, objectsByFeatureId);
    });
    INFO() << concat("features to blur ", features.size());
    auto guard = std::mutex{};
    auto processed = size_t{};
    auto affected = size_t{};
    parallelForEachBatch<ThreadsNumber, BatchSize>(features, [&](auto batch) {
        auto updating =
            updateFeatures(pool, mds, batch, objectsByFeatureId, dryRun);
        auto lock = std::lock_guard{guard};
        auto prev = processed;
        processed += std::size(batch);
        affected += updating;
        if (prev < ceil(processed, 100 * BatchSize)) {
            INFO() << concat("processed ", processed, " affected ", affected);
        }
    });
    INFO() << concat("processed ", processed, " affected ", affected);
}

}  // namespace maps::mrc::blur_mds
