#include "delete_queue.h"
#include "config.h"
#include "yandex/maps/wiki/common/pg_utils.h"
#include <maps/libs/common/include/make_batches.h>
#include <maps/libs/log8/include/log8.h>

using namespace std::literals::chrono_literals;

namespace maps::wiki::merge_poi {

namespace {
const auto DELETE_QUEUE_TABLE = "sprav.merge_poi_worker_delete_queue"s;
const chrono::TimePoint::duration MAX_QUEUE_AGE = 720h;
const size_t BATCH_SIZE = 1000;
} // namespace

DeleteQueue::DeleteQueue(const Config& cfg)
    : cfg_(cfg)
    , now_(std::chrono::system_clock::now())
{
    load();
}

void
DeleteQueue::load()
{
    auto socialTxn = cfg_.socialPool().slaveTransaction();
    auto rows = socialTxn->exec("SELECT object_id, delete_since FROM " + DELETE_QUEUE_TABLE);
    objectToDeleteSince_.reserve(rows.size());
    for (const auto& row : rows) {
        objectToDeleteSince_.emplace(row[0].as<ObjectId>(), chrono::parseSqlDateTime(row[1].c_str()));
    }
    INFO() << "Delete queue size: " << objectToDeleteSince_.size() << " objects";
}

void
DeleteQueue::save() const
{
    auto socialTxn = cfg_.socialPool().masterWriteableTransaction();
    if (!toRemoveFromQueue_.empty()) {
        auto batches = maps::common::makeBatches(toRemoveFromQueue_, BATCH_SIZE);
        for (const auto& batch : batches) {
            socialTxn->exec(
                "DELETE FROM " + DELETE_QUEUE_TABLE +
                " WHERE object_id IN (" + common::join(batch, ",") + ")");
        }
    }
    if (!newToDelete_.empty()) {
        auto batches = maps::common::makeBatches(newToDelete_, BATCH_SIZE);
        for (const auto& batch : batches) {
            socialTxn->exec(
                "INSERT INTO " + DELETE_QUEUE_TABLE +
                "(object_id) VALUES (" +
                common::join(batch, "),(") +
                ")");
        }
    }
    socialTxn->commit();
}

std::unordered_set<poi_feed::ObjectId>
DeleteQueue::notYetToDelete(poi_feed::FeedObjectDataVector& patches)
{
    std::unordered_set<ObjectId> seenSignals;
    std::unordered_set<poi_feed::ObjectId> notReadyToBeDeleted;
    seenSignals.reserve(patches.size());
    for (const auto& patch : patches) {
        const auto id = patch.nmapsId();
        seenSignals.insert(id);
        if (patch.toDelete()) {
            auto queueAge = age(id);
            if (!queueAge) {
                enqueue(id);
                notReadyToBeDeleted.insert(id);
            } else if (queueAge < MAX_QUEUE_AGE) {
                notReadyToBeDeleted.insert(id);
            }
        } else {
            dequeue(id);
        }
    }
    for (const auto& [id, _] : objectToDeleteSince_) {
        if (!seenSignals.count(id)) {
            dequeue(id);
        }
    }
    return notReadyToBeDeleted;
}

std::optional<chrono::TimePoint::duration>
DeleteQueue::age(ObjectId id) const
{
    auto it = objectToDeleteSince_.find(id);
    return it == objectToDeleteSince_.end()
        ? std::optional<chrono::TimePoint::duration>()
        : now_ - it->second;
}

void
DeleteQueue::enqueue(ObjectId id)
{
    newToDelete_.insert(id);
}

void
DeleteQueue::dequeue(ObjectId id)
{
    if (objectToDeleteSince_.count(id)) {
        toRemoveFromQueue_.insert(id);
    }
}
} // namespace maps::wiki::merge_poi
