#include <maps/wikimap/mapspro/services/mrc/libs/common/include/persistent_queue.h>

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/algorithm/retry.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include "sqlite_db.h"

namespace maps {
namespace mrc {
namespace common {
namespace {

const std::string TABLE = "queue";

namespace col {

const std::string ID = "id";
const std::string DATA = "data";

} // namespace col

namespace query {

const std::string CREATE = "CREATE TABLE IF NOT EXISTS " + TABLE +
    " (" + col::ID + " INTEGER PRIMARY KEY ASC, " + col::DATA + " BLOB)";
const std::string DELETE = "DELETE FROM " + TABLE +
    " WHERE " + col::ID + " = ?";
const std::string INSERT = "INSERT INTO " + TABLE +
    "(" + col::DATA + ") VALUES (?)";
const std::string SELECT = "SELECT " + col::ID + ", " + col::DATA + " FROM " + TABLE +
    " ORDER BY " + col::ID + " LIMIT 1";
const std::string COUNT = "SELECT COUNT(*) FROM " + TABLE;

} // namespace query

template <typename Functor>
auto retryOnBusyException(Functor&& func) -> decltype(func()) {
    // Retry during 30 seconds
    return retryOnException<SQLiteBusy>(
        RetryPolicy()
            .setInitialTimeout(std::chrono::milliseconds(50))
            .setMaxAttempts(600)
            .setTimeoutBackoff(1),
        func);
}

} // namespace

PersistentQueue::PersistentQueue(std::string path)
    : path_{std::move(path)} {
    retryOnBusyException([this] {
        SQLiteDb db(path_);
        db.exec("PRAGMA auto_vacuum = FULL");
        db.exec(query::CREATE);
    });
}

boost::optional<std::string> PersistentQueue::pop() {
    return retryOnBusyException([this]() -> boost::optional<std::string> {
        SQLiteDb db(path_);
        db.exec("BEGIN");
        db.exec(query::SELECT);
        std::vector<SQLiteVariant> row;
        if (db.fetch(row)) {
            auto id = numericCast<SQLiteId>(row[0]);
            auto& data = boost::get<SQLiteBlob>(row[1]);
            db.exec(query::DELETE, {id});
            REQUIRE(db.changes() == 1, "SQLite deleting error");
            db.exec("COMMIT");
            return std::string{data.begin(), data.end()};
        } else {
            return boost::none;
        }
    });
}

void PersistentQueue::push(const std::string& data) {
    retryOnBusyException([this, &data] {
        SQLiteDb db(path_);
        db.exec(query::INSERT, {SQLiteBlob{data.begin(), data.end()}});
        REQUIRE(db.changes() == 1, "SQLite inserting error");
    });
}

size_t PersistentQueue::size() {
    return retryOnBusyException([this] {
        SQLiteDb db(path_, SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX);
        db.exec(query::COUNT);
        std::vector<SQLiteVariant> row;
        REQUIRE(db.fetch(row), "SQLite select count(*) error");
        return numericCast<size_t>(row[0]);
    });
}

} // namespace common
} // namespace mrc
} // namespace maps
