#include "get_feedback_task_history.h"
#include <maps/wikimap/mapspro/services/editor/src/branch_helpers.h>
#include <maps/wikimap/mapspro/services/editor/src/exception.h>

#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/commit.h>
#include <yandex/maps/wiki/social/gateway.h>
#include <yandex/maps/wiki/social/feedback/task.h>
#include <yandex/maps/wiki/social/feedback/commits.h>

#include <algorithm>
#include <unordered_map>

namespace maps {
namespace wiki {

namespace {

static const std::string TASK_METHOD_NAME = "GetFeedbackTaskHistory";

} // namespace

GetSocialFeedbackTaskHistory::GetSocialFeedbackTaskHistory(const Request& request)
    : controller::BaseController<GetSocialFeedbackTaskHistory>(BOOST_CURRENT_FUNCTION)
    , request_(request)
{
    WIKI_REQUIRE(
        !request_.afterEventId || !request_.beforeEventId,
        ERR_BAD_REQUEST,
        "non-empty after,before request parameters");
    WIKI_REQUIRE(
        request_.perPage >= 1,
        ERR_BAD_REQUEST,
        "per page < 1");

    result_->hasMore = false;
    result_->perPage = request_.perPage;
}

std::string
GetSocialFeedbackTaskHistory::printRequest() const
{
    std::stringstream ss;
    ss << " uid: " << request_.uid;
    ss << " feedback-task-id: " << request_.feedbackTaskId;
    ss << " token: " << request_.token;
    if (request_.beforeEventId) {
        ss << " before-event-id: " << *request_.beforeEventId;
    }
    if (request_.afterEventId) {
        ss << " after-event-id: " << *request_.afterEventId;
    }
    ss << " per-page: " << request_.perPage;
    return ss.str();
}

void
GetSocialFeedbackTaskHistory::control()
{
    auto branchCtx = BranchContextFacade().acquireReadCoreSocial(request_.token);
    social::Gateway sgw(branchCtx.txnSocial());

    auto allCommitIds = social::feedback::commitIdsByTaskId(branchCtx.txnSocial(), request_.feedbackTaskId);
    auto allEvents = sgw.loadEditEventsByCommitIds(allCommitIds);
    allEvents.sort(
        [](const social::Event& left, const social::Event& right){
            return left.id() > right.id();
        });

    boost::optional<social::TId> anchorEventId;
    boost::optional<actions::AnchorDirection> anchorDirection;
    if (request_.beforeEventId) {
        anchorEventId = request_.beforeEventId;
        anchorDirection = actions::AnchorDirection::Before;
    }
    if (request_.afterEventId) {
        anchorEventId = request_.afterEventId;
        anchorDirection = actions::AnchorDirection::After;
    }

    using EventPredicate = std::function<bool (const social::Event&)>;
    EventPredicate anchorPredicate;
    if (anchorEventId) {
        anchorPredicate = [&](const social::Event& event) {
            return event.id() == *anchorEventId;
        };
    }

    actions::Page<social::Events> eventsPage = actions::fetchPage(
        allEvents,
        request_.perPage,
        anchorPredicate,
        anchorDirection);
    result_->hasMore = eventsPage.hasMore;
    // commits loading with empty filter throws an exception
    if (eventsPage.items.empty()) {
        return;
    }

    TCommitIds commitIds;
    for (const auto& event: eventsPage.items) {
        ASSERT(event.commitData());
        commitIds.insert(event.commitData()->commitId());
    }
    auto filter = revision::filters::CommitAttr::id().in(commitIds);
    auto commits = revision::Commit::load(branchCtx.txnCore(), filter);
    ASSERT(commits.size() == eventsPage.items.size());
    auto eventIdToApproveStatus = approve_status::evalEventsApproveStatuses(branchCtx, eventsPage.items, commits);

    BatchCommitPreparedFields batchPreparedFields;
    batchPreparedFields.prepareStates(branchCtx, commits);
    batchPreparedFields.prepareLastFlags(branchCtx, commits, revision::TRUNK_BRANCH_ID);

    std::unordered_map<TCommitId, revision::Commit> commitIdToCommit;
    for (auto& commit: commits) {
        commitIdToCommit.emplace(commit.id(), std::move(commit));
    }

    for (auto& event: eventsPage.items) {
        ASSERT(event.commitData());
        auto commit = commitIdToCommit.at(event.commitData()->commitId());

        CommitModel commitModel(std::move(commit));
        batchPreparedFields.fillCommitModel(commitModel);
        commitModel.setApproveStatus(eventIdToApproveStatus.at(event.id()));

        result_->eventModels.emplace_back(
            controller::ResultType<GetSocialFeedbackTaskHistory>::EventModel{std::move(event), std::move(commitModel)}
        );
    }
}

const std::string&
GetSocialFeedbackTaskHistory::taskName()
{
    return TASK_METHOD_NAME;
}

} // namespace wiki
} // namespace maps
