#include "get_history.h"
#include "maps/wikimap/mapspro/services/editor/src/configs/config.h"
#include "maps/wikimap/mapspro/services/editor/src/branch_helpers.h"
#include "maps/wikimap/mapspro/services/editor/src/common.h"
#include "maps/wikimap/mapspro/services/editor/src/commit.h"
#include "maps/wikimap/mapspro/services/editor/src/objects_cache.h"

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

#include <iterator>
#include <algorithm>
#include <cmath>

namespace maps {
namespace wiki {

GetHistory::GetHistory(const Request& request)
    : controller::BaseController<GetHistory>(BOOST_CURRENT_FUNCTION)
    , request_(request)
{
    result_->totalCount  = 0;
    result_->page = request_.page;
    result_->perPage = request_.perPage;
    result_->oid = request_.oid;
}

std::string
GetHistory::printRequest() const
{
    std::stringstream ss;
    ss << " user: " << request_.user;
    ss << " oid: " << request_.oid;
    ss << " token: " << request_.token;
    ss << " page: " << request_.page;
    ss << " per-page: " << request_.perPage;
    ss << " branch-id: " << request_.branchId;
    ss << " with-relations-change: " << (request_.relationsChangePolicy == RelationsChangePolicy::Show);
    ss << " primary-only: " << (request_.indirectChangePolicy == IndirectChangePolicy::Hide);
    return ss.str();
}

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

    revision::RevisionsGateway gateway(branchCtx.txnCore(), branchCtx.branch);
    auto snapshot = gateway.historicalSnapshot(gateway.headCommitId());

    auto revsFilter =
        (revision::filters::ObjRevAttr::objectId() == request_.oid &&
            revision::filters::ObjRevAttr::isNotRelation());

    auto revisionIds = snapshot.revisionIdsByFilter(revsFilter);
    std::set<revision::DBID> commitIds;
    for (const auto& revId : revisionIds) {
        commitIds.insert(revId.commitId());
    }
    if (request_.relationsChangePolicy == RelationsChangePolicy::Show) {
        auto relsFilter =
            (revision::filters::ObjRevAttr::isRelation() &&
                (revision::filters::ObjRevAttr::slaveObjectId() == request_.oid ||
                 revision::filters::ObjRevAttr::masterObjectId() == request_.oid));
        auto relationsIds = snapshot.revisionIdsByFilter(relsFilter);
        for (const auto& revId : relationsIds) {
            commitIds.insert(revId.commitId());
        }
    }
    TCommitId firstCommitId = commitIds.empty()
        ? 0
        : *commitIds.cbegin();
    StringVec commitAttrs {
            objectEditNotesKey(request_.oid),
            primaryObjectKey(request_.oid)
        };
    auto commitsFilter =
        revision::filters::CommitAttribute::definedAny(commitAttrs)
        && revision::filters::CommitAttr::isVisible(branchCtx.branch);
    auto commits = revision::Commit::load(branchCtx.txnCore(), commitsFilter);
    std::map<TCommitId, const revision::Commit*> commitsCache;
    for (const auto& commit : commits) {
        if (firstCommitId != commit.id() &&
            request_.indirectChangePolicy == IndirectChangePolicy::Hide) {
            auto optionalPrimaryId = primaryObjectId(commit);
            if (optionalPrimaryId != request_.oid) {
                commitIds.erase(commit.id());
                continue;
            }
        }
        commitIds.insert(commit.id());
        commitsCache.insert({commit.id(), &commit});
    }

    result_->totalCount = commitIds.size();
    size_t startIndex = request_.page * request_.perPage - request_.perPage;
    if (result_->totalCount <= startIndex) {
        return;
    }
    auto begin = commitIds.rbegin();
    std::advance(begin, startIndex);
    auto end = begin;
    std::advance(end, std::min(request_.perPage, commitIds.size() - startIndex));
    std::vector<TCommitId> notCached;
    std::list<revision::Commit> resultCommits;
    for (auto commitIdIt = begin; commitIdIt != end;  ++commitIdIt) {
        auto itCommit = commitsCache.find(*commitIdIt);
        if (itCommit == commitsCache.end()) {
            notCached.push_back(*commitIdIt);
        } else {
            resultCommits.push_back(*itCommit->second);
        }
    }
    if (!notCached.empty()) {
        auto filter = revision::filters::CommitAttr::id().in(notCached);
        auto remainingCommits = revision::Commit::load(branchCtx.txnCore(), filter);
        resultCommits.splice(resultCommits.end(), remainingCommits);
    }


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

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

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

} // namespace wiki
} // namespace maps
