#include "get_dependent.h"
#include <maps/wikimap/mapspro/services/editor/src/actions/magic_strings.h>
#include <maps/wikimap/mapspro/services/editor/src/acl_utils.h>
#include <maps/wikimap/mapspro/services/editor/src/branch_helpers.h>
#include <maps/wikimap/mapspro/services/editor/src/configs/config.h>
#include <maps/wikimap/mapspro/services/editor/src/exception.h>

#include <maps/libs/common/include/profiletimer.h>
#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/commit_manager.h>

#include <maps/wikimap/mapspro/libs/acl_utils/include/moderation.h>

#include <iterator>
#include <optional>

namespace maps {
namespace wiki {

namespace {

const auto FIND_DEPENDENT_COMMITS_DEADLINE_TIMEOUT = 20; // seconds;
const auto REVERTER_COMMITS_LIMIT = 1000;

std::vector<TCommitId>
getPage(const TCommitIds& commitIds, size_t page, size_t perPage)
{
    if (!perPage) {
        return {};
    }

    auto total = commitIds.size();
    auto startIndex = (page - 1) * perPage;

    if (startIndex >= total) {
        return {};
    }

    std::vector<TCommitId> result;

    auto begin = commitIds.rbegin();
    std::advance(begin, startIndex);
    auto end = begin;

    auto count = std::min(perPage, total - startIndex);
    std::advance(end, count);

    result.reserve(count);
    for (auto it = begin; it != end;  ++it) {
        result.push_back(*it);
    }
    return result;
}

} // namespace

void
GetDependentCommits::Request::check() const
{
    CHECK_REQUEST_PARAM(uid);
    CHECK_REQUEST_PARAM(commitId);
    CHECK_REQUEST_PARAM(page);
}

std::string
GetDependentCommits::Request::dump() const
{
    std::stringstream ss;
    ss << " uid: " << uid;
    ss << " commit: " << commitId;
    ss << " token: " << token;
    ss << " page: " << page;
    ss << " per-page: " << perPage;
    return ss.str();
}

GetDependentCommits::GetDependentCommits(const Request& request)
    : controller::BaseController<GetDependentCommits>(BOOST_CURRENT_FUNCTION)
    , request_(request)
{
    request_.check();

    result_->totalCount  = 0;
    result_->page = request_.page;
    result_->perPage = request_.perPage;
}

std::string
GetDependentCommits::printRequest() const
{
    return request_.dump();
}

void
GetDependentCommits::control()
{
    auto branchCtx = BranchContextFacade().acquireReadCoreSocial(request_.token);

    auto user = getUser(branchCtx, request_.uid);
    user.checkActiveStatus();

    std::optional<bool> cachedRevertRole;
    auto cachedHasRevertRole = [&]() {
        if (!cachedRevertRole) {
            cachedRevertRole = acl_utils::hasRole(user, REVERT_ROLE);
        }
        return *cachedRevertRole;
    };

    // let commits limit = objects limit
    const auto userLimit = cfg()->editor()->system().revertObjectsLimit();

    ProfileTimer pt;
    auto resultChecker = [&](const auto& commitIds) {
        auto elapsedTime = pt.getElapsedTimeNumber();
        auto commitsSize = commitIds.size();
        WIKI_REQUIRE(
            elapsedTime <= FIND_DEPENDENT_COMMITS_DEADLINE_TIMEOUT
                && commitsSize <= REVERTER_COMMITS_LIMIT
                && (commitsSize <= userLimit || cachedHasRevertRole()),
            ERR_REVERT_TOO_COMPLEX,
            "Too complex revert."
            " Commits: " << commitsSize << " Elapsed time: " << elapsedTime);
        return revision::SearchPredicateResult::Continue;
    };

    revision::CommitManager commitManager(branchCtx.txnCore());

    auto commitIds = commitManager
        .findDependentCommitsInTrunk(TCommitIds{request_.commitId}, resultChecker);
    WIKI_REQUIRE(
        !commitIds.empty(),
        ERR_NOT_FOUND,
        "Commit id: " << request_.commitId << " not found");

    auto deleted = commitIds.erase(request_.commitId);
    WIKI_REQUIRE(
        deleted > 0,
        ERR_NOT_FOUND,
        "Commit id: " << request_.commitId << " not skipped");

    result_->totalCount = commitIds.size();

    auto pageCommitIds = getPage(commitIds, request_.page, request_.perPage);
    if (pageCommitIds.empty()) {
        return;
    }

    auto filter = revision::filters::CommitAttr::id().in(pageCommitIds);
    auto resultCommits = revision::Commit::load(branchCtx.txnCore(), filter);
    auto resultCommitsSize = resultCommits.size();

    REQUIRE(
        pageCommitIds.size() == resultCommitsSize,
        "Can not load dependent commits,"
        " in page: " << pageCommitIds.size() << " from db: " << resultCommitsSize);

    resultCommits.sort(
        [](const auto& left, const auto& right){
            return left.id() > right.id();
        });

    BatchCommitPreparedFields batchPreparedFields;
    batchPreparedFields.prepareStates(branchCtx, resultCommits);
    batchPreparedFields.prepareApproveStatuses(branchCtx, resultCommits);

    result_->commitModels.reserve(resultCommitsSize);
    for (auto& commit: resultCommits) {
        CommitModel commitModel(std::move(commit));
        batchPreparedFields.fillCommitModel(commitModel);
        result_->commitModels.push_back(std::move(commitModel));
    }
}

} // namespace wiki
} // namespace maps
