#include "updater.h"
#include "update_queue.h"
#include "bbox_update_task.h"

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

#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <yandex/maps/wiki/threadutils/threadpool.h>

#include <chrono>
#include <sstream>

namespace maps {
namespace wiki {

namespace {

const std::chrono::seconds UPDATE_DELAY(60);

} // namespace


RevisionMetaUpdater::RevisionMetaUpdater(
    TBranchId branchId, size_t threadPoolSize
)
    : ThreadObserver<RevisionMetaUpdater>(UPDATE_DELAY)
    , branchId_(branchId)
    , threadPoolSize_(threadPoolSize)
    , name_("Branch " + boost::lexical_cast<std::string>(branchId_) + " revision_meta cache updater")
{
    start();
}

void
RevisionMetaUpdater::doWork()
try {
    auto branchCtx = BranchContextFacade(branchId_).acquireWrite();
    if (branchCtx.branch.state() != revision::BranchState::Normal) {
        return;
    }
    auto& mainTxn = branchCtx.txnCore();
    if (!branchCtx.branch.tryLock(mainTxn, revision_meta::BRANCH_LOCK_ID)) {
        return;
    }
    revision_meta::CommitsQueue commitsQueue(mainTxn, branchId_);
    const auto queuedCommitIds = commitsQueue.lockCommits(boost::none, boost::none);
    DEBUG() << name() << ", commits to process count: " <<  queuedCommitIds.size();
    if (queuedCommitIds.empty()) {
        return;
    }

    TCommitId minCommitId = *queuedCommitIds.begin();
    ComputeBBoxesParams bboxParams{
        BBoxUpdateAction::Both,
        branchId_,
        minCommitId,
        revision::RevisionsGateway(mainTxn, branchCtx.branch).headCommitId(),
        {}, //process all commits
        COMMIT_RANGE_BATCH_SIZE,
        cfg()->editor()->categories().idsByFilter(Categories::CachedGeom),
        threadPoolSize_,
        make_unique<ExecutionState>()
    };

    ComputeBBoxTask task(bboxParams);

    task.exec(mainTxn);
    commitsQueue.deleteCommits(queuedCommitIds);
    mainTxn.commit();
} catch (const maps::Exception& e) {
    WARN() << "Updating revision_meta for branch "
           << branchId_ << ": " << e;
} catch (const std::exception& e) {
    WARN() << "Updating revision_meta for branch "
           << branchId_ << ": " << e.what();
} catch (...) {
    WARN() << "Updating revision_meta for branch "
           << branchId_ << ": unknown error";
}

/// Main revision_meta cache updater

RevisionMetaUpdatersManager::RevisionMetaUpdatersManager(
        size_t threadsPerUpdater)
    : ThreadObserver<RevisionMetaUpdatersManager>(UPDATE_DELAY)
    , threadsPerUpdater_(threadsPerUpdater)
{
    start();
}

void
RevisionMetaUpdatersManager::doWork()
try {
    auto writeTxn = cfg()->poolCore().masterWriteableTransaction();
    revision::BranchManager branchManager(*writeTxn);
    revision::BranchManager::BranchLimits branchLimits{
        {revision::BranchType::Trunk, 0},
        {revision::BranchType::Stable, 0},
        {revision::BranchType::Archive, 0}};
    revision::BranchManager::Branches actualBranches = branchManager.load(branchLimits);
    std::set<TBranchId> actualBranchIds;
    for (const auto& branch : actualBranches) {
        actualBranchIds.insert(branch.id());
    }
    startActualBranchWorkers(std::move(actualBranches));
    stopObsoleteBranchWorkers(actualBranchIds, *writeTxn);
} catch (const maps::Exception& e) {
    ERROR() << name() << " error: " << e;
} catch (const std::exception& e) {
    ERROR() << name() << " error: " << e.what();
} catch (...) {
    ERROR() << name() << " unknown error";
}

// start workers for actual (trunk, stable, archive's) branches
void
RevisionMetaUpdatersManager::startActualBranchWorkers(
    revision::BranchManager::Branches&& actualBranches)
{
    for (auto&& branch : actualBranches) {
        const TBranchId branchId = branch.id();

        if (updaters_.count(branchId)) {
            continue;
        }

        updaters_.insert(std::make_pair(
            branchId,
            make_unique<RevisionMetaUpdater>(branchId, threadsPerUpdater_)));
        INFO() << "Created revision_meta updater for branch " << branchId;
    }
}

// terminate workers for obsolete deleted branches
void
RevisionMetaUpdatersManager::stopObsoleteBranchWorkers(
    const std::set<TBranchId>& actualBranchIds,
    Transaction& txn)
{
    for (auto it = updaters_.begin(); it != updaters_.end(); ) {
        auto branchId = it->first;
        if (!actualBranchIds.count(branchId)) {
            revision::BranchManager branchManager(txn);
            auto branch = branchManager.load(branchId);
            if (branch.tryLock(txn, revision_meta::BRANCH_LOCK_ID)) {
                INFO() << "Removing revision_meta updater for branch " << branchId;
                it = updaters_.erase(it);
                continue;
            }
        }
        ++it;
    }
}

} // namespace wiki
} // namespace maps
