#include "db_helpers.h"
#include "lock_helpers.h"

#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>

#include <maps/wikimap/mapspro/libs/views/include/magic_strings.h>
#include <yandex/maps/wiki/common/pg_utils.h>
#include <yandex/maps/wiki/revision/branch_manager.h>
#include <maps/libs/common/include/profiletimer.h>
#include <maps/libs/log8/include/log8.h>

namespace maps {
namespace wiki {
namespace sync {

namespace {

const size_t OBJECTS_BATCH_COUNT = 1000;
const size_t TOTAL_COUNT_LIMIT = 100000;
const double TIMER_LIMIT_SEC = 10; //seconds

TOIds lockObjects(pqxx::transaction_base& work, TId branchMaskId, const std::string& tableName)
{
    auto queryObjects = "SELECT id FROM vrevisions_stable." + tableName
        + " WHERE branch_mask_id=" + std::to_string(branchMaskId)
        + " ORDER BY id, branch_mask_id"
        + " LIMIT " + std::to_string(OBJECTS_BATCH_COUNT)
        + " FOR UPDATE";

    TOIds objectIds;
    for (const auto& row : work.exec(queryObjects)) {
        objectIds.insert(row[0].as<TOid>());
    }
    return objectIds;
}

template<typename Func>
bool processObjects(
    pqxx::transaction_base& work,
    TId branchMaskId,
    Func generateQuery)
{
    ProfileTimer pt;
    size_t totalCount = 0;
    std::set<std::string> tableNames;

    for (const auto& tableName : views::VIEW_TABLES) {
        INFO() << "Branch mask cleaner: start processing table " << tableName;

        while (totalCount == 0 ||
                (totalCount < TOTAL_COUNT_LIMIT && pt.getElapsedTimeNumber() < TIMER_LIMIT_SEC)) {
            tableNames.insert(tableName);

            auto objectIds = lockObjects(work, branchMaskId, tableName);
            if (objectIds.empty()) {
                break;
            }

            INFO() << "Branch mask cleaner: locked " << objectIds.size() << " objects "
                << "in table " << tableName;

            work.exec(generateQuery(tableName, objectIds));

            totalCount += objectIds.size();
        }
    }

    INFO() << "Branch mask cleaner: processed " << totalCount << " objects";

    return tableNames.size() == views::VIEW_TABLES.size()
        && totalCount == 0; //do not check timer here to enforce row deletion from branch_mask table
}

boost::optional<TId> cleanUpEmptyBranchMaskId()
{
    if (!cfg()->branchMaskCleanEmptyEnabled()) {
        INFO() << "Branch mask cleaner: clean operation is disabled";
        return boost::none;
    }

    auto work = cfg()->poolViewStable().masterWriteableTransaction();

    lockViews(*work, LockType::Shared);

    INFO() << "Branch mask cleaner: select empty branch mask id";

    const auto preloadQuery =
        "SELECT branch_mask_id"
        "  FROM vrevisions_stable.branch_mask"
        "  WHERE branches=''::hstore"
        "  ORDER BY 1"
        "  LIMIT 1";

    auto result = work->exec(preloadQuery);
    if (result.empty()) {
        INFO() << "Branch mask cleaner: there are no empty branch mask ids";
        return boost::none;
    }

    auto emptyBranchMaskId = result[0][0].as<TId>();

    INFO() << "Branch mask cleaner: empty branch mask id " << emptyBranchMaskId;

    bool finished = processObjects(
        *work,
        emptyBranchMaskId,
        [&](const std::string& tableName, const TOIds& objectIds) {
            return "DELETE FROM vrevisions_stable." + tableName
                + " WHERE " + common::whereClause(ID, objectIds)
                + "   AND branch_mask_id=" + std::to_string(emptyBranchMaskId);
        });

    work->commit();

    if (finished) {
        return emptyBranchMaskId;
    }
    return boost::none;
}

boost::optional<TId> mergeDuplicateBranchesWithMaxId(const revision::Branch& approvedBranch)
{
    if (!cfg()->branchMaskMergeDuplicateEnabled()) {
        INFO() << "Branch mask cleaner: merge operation is disabled";
        return boost::none;
    }

    auto work = cfg()->poolViewStable().masterWriteableTransaction();

    lockViews(*work, LockType::Shared);

    INFO() << "Branch mask cleaner: select duplicate branch mask ids";

    const auto preloadQuery =
        "SELECT MIN(branch_mask_id), MAX(branch_mask_id), ARRAY_LENGTH(akeys(branches), 1) AS cnt "
        "FROM vrevisions_stable.branch_mask "
        "WHERE branches ? '" + std::to_string(approvedBranch.id()) + "' "
        "GROUP BY branches "
        "HAVING COUNT(branch_mask_id) > 1 "
        "ORDER BY cnt DESC "
        "LIMIT 1";

    auto result = work->exec(preloadQuery);
    if (result.empty()) {
        INFO() << "Branch mask cleaner: there are no duplicate branch mask ids";
        return boost::none;
    }
    ASSERT(result.size() == 1);
    auto minId = result[0][0].as<TId>();
    auto maxId = result[0][1].as<TId>();

    INFO() << "Branch mask cleaner: merge ids " << maxId << "->" << minId;

    bool finished = processObjects(
        *work,
        maxId,
        [&](const std::string& tableName, const TOIds& objectIds) {
            return "UPDATE vrevisions_stable." + tableName
                + " SET branch_mask_id=" + std::to_string(minId)
                + " WHERE " + common::whereClause(ID, objectIds)
                + "   AND branch_mask_id=" + std::to_string(maxId);
        });

    work->commit();

    if (finished) {
        return maxId;
    }
    return boost::none;
}

} // namespace

void cleanUpBranchMaskTable()
{
    auto workCore = masterCoreTransaction(AccessMode::ReadWrite);

    if (!tryLockDb(*workCore, common::AdvisoryLockIds::BRANCH_MASK_CLEANER, LockType::Exclusive)) {
        INFO() << "Branch mask cleaner skip: another instance is already running";
        return;
    }

    revision::BranchManager branchManager(*workCore);
    auto approvedBranch = branchManager.loadApproved();
    if (approvedBranch.state() != revision::BranchState::Normal) {
        INFO() << "Branch mask cleaner skip: approved branch is not in normal state";
        return;
    }

    auto branchMaskIdToDelete = cleanUpEmptyBranchMaskId();
    if (!branchMaskIdToDelete) {
        branchMaskIdToDelete = mergeDuplicateBranchesWithMaxId(approvedBranch);
    }

    if (!branchMaskIdToDelete) {
        INFO() << "Branch mask cleaner finish: no branch mask id to delete";
        return;
    }

    auto workView = cfg()->poolViewStable().masterWriteableTransaction();

    if (!tryLockViews(*workView, LockType::Exclusive)) {
        INFO() << "Branch mask cleaner skip: view is already locked";
        return;
    }

    INFO() << "Branch mask cleaner: delete branch mask id " << *branchMaskIdToDelete;

    workView->exec("DELETE FROM vrevisions_stable.branch_mask"
        " WHERE branch_mask_id=" + std::to_string(*branchMaskIdToDelete));

    workView->commit();

    INFO() << "Branch mask cleaner finish";
}

} // namespace sync
} // namespace wiki
} // namespace maps
