#include "deleter.h"

#include <yandex/maps/wiki/revision/branch_manager.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <yandex/maps/wiki/revision/common.h>
#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/branch_manager.h>

namespace revision = maps::wiki::revision;
namespace filters = revision::filters;


namespace maps::wiki::revisionapi {

namespace {
const revision::Attributes COMMIT_ATTRS = {{"action", "group-deleted"}};
const size_t ATTEMPTS = 5;

void
checkIdExistence(
    const std::vector<revision::DBID>& objectIds,
    const std::vector<revision::RevisionID>& revIds)
{
    revision::DBIDSet loadedObjectIds;
    for (const auto& revId : revIds) {
        loadedObjectIds.insert(revId.objectId());
    }
    for (auto objectId : objectIds) {
        if (!loadedObjectIds.contains(objectId)) {
            std::cerr << "Non-existent object id: " << objectId << std::endl;
        }
    }
}

revision::Revisions
loadRevisions(
    pgpool::Pool& pool,
    revision::DBID branchId,
    const std::vector<revision::DBID>& objectIds,
    VerboseLevel verboseLevel)
{
    auto writeTr = pgpool::makeReadOnlyTransaction(pool.getMasterConnection());
    auto branch = revision::BranchManager(*writeTr).load(branchId);
    REQUIRE(branch.isWritingAllowed(),
            "writing into trunk branch unallowed, state: " << branch.state());

    revision::RevisionsGateway gw(*writeTr, branch);
    auto snapshot = gw.snapshot(gw.headCommitId());
    auto revIds = snapshot.objectRevisionIds(objectIds);

    if (verboseLevel == VerboseLevel::Full) {
        checkIdExistence(objectIds, revIds);
    }
    auto revisions = gw.reader().loadRevisions(revIds);

    filters::ProxyFilterExpr relFilter(filters::ObjRevAttr::masterObjectId().in(objectIds));
    relFilter |= filters::ObjRevAttr::slaveObjectId().in(objectIds);
    relFilter &= filters::ObjRevAttr::isNotDeleted();
    auto relRevisions = snapshot.relationsByFilter(relFilter);

    revisions.splice(revisions.end(), relRevisions);

    return revisions;
}

revision::DBID
createCommit(
    pgpool::Pool& pool,
    revision::DBID branchId,
    const std::list<revision::RevisionsGateway::NewRevisionData>& newObjectsData,
    revision::UserID userId)
{
    auto writeTr = pgpool::makeWriteableTransaction(pool.getMasterConnection());
    auto branch = revision::BranchManager(*writeTr).load(branchId);
    revision::RevisionsGateway gw(*writeTr, branch);

    auto commit = gw.createCommit(newObjectsData, userId, COMMIT_ATTRS);
    writeTr->commit();

    return commit.id();
}

} // namespace

revision::DBID
deleteBatchObjects(
    pgpool::Pool& pool,
    revision::DBID branchId,
    VerboseLevel verboseLevel,
    const std::vector<revision::DBID>& objectIds,
    revision::UserID userId)
{
    auto revs = loadRevisions(pool, branchId, objectIds, verboseLevel);

    std::list<revision::RevisionsGateway::NewRevisionData> newObjectsData;
    for (const auto& rev : revs) {
        const revision::ObjectRevision::Data& data = rev.data();
        if (data.deleted == true) {
            if (verboseLevel == VerboseLevel::Full) {
                std::cerr << "Skipping deleted object " << rev.id() << ".\n";
            }
            continue;
        }
        revision::RevisionsGateway::NewRevisionData newObjData;
        newObjData.first = rev.id();
        newObjData.second.attributes = *data.attributes;
        newObjData.second.deleted = true;
        newObjectsData.push_back(std::move(newObjData));
    }

    if (newObjectsData.empty()) {
        return 0;
    }

    return createCommit(pool, branchId, newObjectsData, userId);
}

std::list<revision::DBID>
deleteObjects(
    pgpool::Pool& pool,
    revision::DBID branchId,
    VerboseLevel verboseLevel,
    const std::vector<revision::DBID>& objectIds,
    revision::UserID userId,
    size_t commitBatchSize)
{
    std::vector<revision::DBID> batchObjectIds;
    std::list<revision::DBID> commitIds;

    auto updateBatch = [&]()
    {
        if (batchObjectIds.empty()) {
            return;
        }

        for (size_t pass = 0; ++pass <= ATTEMPTS; ) {
            try {
                auto commitId = deleteBatchObjects(pool, branchId, verboseLevel, batchObjectIds, userId);
                if (commitId) {
                    commitIds.push_back(commitId);
                }
                break;
            } catch (const std::exception& e) {
                if (verboseLevel == VerboseLevel::Full) {
                    std::cerr << "Attempt: " << pass << "/" << ATTEMPTS << " : " << e.what() << std::endl;
                }
                if (pass == ATTEMPTS) {
                    throw;
                }
            }
        }
    };

    for (auto oid : objectIds) {
        batchObjectIds.push_back(oid);
        if (commitBatchSize > 0 && batchObjectIds.size() >= commitBatchSize) {
            updateBatch();
            batchObjectIds.clear();
        }
    }
    updateBatch();

    return commitIds;
}

} // namespace maps::wiki::revisionapi
