#include "task_data_substitution.h"

#include <yandex/maps/wiki/social/feedback/consts.h>

namespace maps::wiki::socialsrv::serialize {

namespace sf = social::feedback;

namespace {

bool equalResolutions(
    const std::optional<sf::TaskResolved>& lhs,
    const std::optional<sf::TaskResolved>& rhs)
{
    return (!lhs && !rhs) ||
           (lhs && rhs && lhs->resolution.verdict() == rhs->resolution.verdict());
}

bool needKeepResolutionsOnUpperLevels(
    const std::optional<sf::TaskResolved>& suggestedTaskResolved,
    const std::optional<sf::TaskResolved>& realTaskResolved)
{
    return realTaskResolved && !equalResolutions(suggestedTaskResolved, realTaskResolved);
}

sf::Verdict getSuggestedVerdict(const sf::HistoryItem& item)
{
    const auto it = item.params().find(sf::HISTORY_PARAM_SUGGESTED_VERDICT);
    REQUIRE(it != item.params().end(), "Suggested verdict is missing");
    return enum_io::fromString<sf::Verdict>(it->second);
}

sf::ProcessingLvl getNewProcessingLvl(const sf::HistoryItem& item)
{
    REQUIRE(item.operation() == sf::TaskOperation::ChangeProcessingLvl,
        "change-processing-lvl history item required");
    const auto it = item.params().find(sf::HISTORY_PARAM_NEW_PROCESSING_LVL);
    REQUIRE(it != item.params().end(), "new-processing-level param is missing");
    return enum_io::fromString<sf::ProcessingLvl>(it->second);
}

sf::RejectReason getRejectReason(
    const sf::HistoryItem& item,
    sf::RejectReason defaultValue = sf::RejectReason::NoData)
{
    const auto it = item.params().find(sf::HISTORY_PARAM_REJECT_REASON);
    if (it == item.params().end()) {
        return defaultValue;
    } else {
        return enum_io::fromString<sf::RejectReason>(it->second);
    }
}

sf::Resolution closingItemToResolution(const sf::HistoryItem& item)
{
    switch (item.operation()) {
        case sf::TaskOperation::Accept:
            return sf::Resolution::createAccepted();
        case sf::TaskOperation::Reject:
            return sf::Resolution::createRejected(getRejectReason(item));
        default:
            REQUIRE(
                false,
                "Expected accept/reject operation instead of '" << item.operation() << "'"
            );
    }
}

sf::TaskOperation getLevelUpOperationSubstitution(const sf::HistoryItem& item)
{
    switch (getSuggestedVerdict(item)) {
        case sf::Verdict::Accepted:
            return sf::TaskOperation::Accept;
        case sf::Verdict::Rejected:
            return sf::TaskOperation::Reject;
    }
}

sf::TaskResolved getTaskResolved(const sf::HistoryItem& item)
{
    return sf::TaskResolved{
        item.modifiedBy(), item.modifiedAt(), closingItemToResolution(item)};
}

const sf::TaskOperations TASK_CLOSING_OPERATIONS{
    sf::TaskOperation::Accept,
    sf::TaskOperation::Reject
};

bool isProcessingLevelDown(const sf::HistoryItem& item)
{
    if (item.operation() == sf::TaskOperation::ProcessingLevelDown) {
        return true;
    }
    if (item.operation() == sf::TaskOperation::ChangeProcessingLvl
        && getNewProcessingLvl(item) == sf::ProcessingLvl::Level0) {
        return true;
    }
    return false;
}

} // namespace

namespace internal {

bool isProcessingLevelUp(const sf::HistoryItem& item)
{
    if (item.operation() == sf::TaskOperation::ProcessingLevelUp) {
        return true;
    }
    if (item.operation() == sf::TaskOperation::ChangeProcessingLvl
        && getNewProcessingLvl(item) == sf::ProcessingLvl::Level1) {
        return true;
    }
    return false;
}

} // namespace internal

SubstitutionData getSubstitutionData(
    const sf::HistoryItems& items,
    const std::optional<sf::TaskResolved>& realTaskResolved,
    const sf::TaskState realTaskState)
{
    sf::HistoryItems historyItems;
    std::optional<sf::TaskResolved> shownTaskResolved;

    std::optional<sf::TaskResolved> suggestedTaskResolved;
    int taskProcessingLevel = sf::PROCESSING_LEVEL_MIN;
    for (const auto& item: items) {
        const auto operation = item.operation();
        if (internal::isProcessingLevelUp(item)) {
            if (taskProcessingLevel == sf::PROCESSING_LEVEL_MIN) {
                auto& newItem = historyItems.emplace_back(
                    item.modifiedAt(),
                    item.modifiedBy(),
                    getLevelUpOperationSubstitution(item),
                    sf::HistoryItemParams(), // Drop lvl-up additional info from params.
                    item.commentId()
                );
                newItem.setId(item.id());
                suggestedTaskResolved = getTaskResolved(newItem);
                shownTaskResolved = suggestedTaskResolved;
            }
            ++taskProcessingLevel;
        } else if (isProcessingLevelDown(item)) {
            --taskProcessingLevel;
            if (taskProcessingLevel == sf::PROCESSING_LEVEL_MIN) {
                suggestedTaskResolved = std::nullopt;
                shownTaskResolved = std::nullopt;
                auto& newItem = historyItems.emplace_back(
                    item.modifiedAt(),
                    item.modifiedBy(),
                    sf::TaskOperation::Open,
                    sf::HistoryItemParams(),
                    item.commentId()
                );
                newItem.setId(item.id());
            }
        } else if (operation == sf::TaskOperation::Open ||
                    operation == sf::TaskOperation::NeedInfo) {
            if (taskProcessingLevel == sf::PROCESSING_LEVEL_MIN) {
                historyItems.push_back(item);
                shownTaskResolved = std::nullopt;
            }
        } else if (TASK_CLOSING_OPERATIONS.count(operation)) {
            if (taskProcessingLevel == sf::PROCESSING_LEVEL_MIN) {
                shownTaskResolved = getTaskResolved(item);
                historyItems.push_back(item);
            } else {
                const auto itemTaskResolved = getTaskResolved(item);
                if (needKeepResolutionsOnUpperLevels(
                        suggestedTaskResolved, realTaskResolved) &&
                        !equalResolutions(
                            shownTaskResolved, itemTaskResolved)) {
                    shownTaskResolved = itemTaskResolved;
                    historyItems.push_back(item);
                }
            }
        } else if (operation == sf::TaskOperation::Deploy) {
                historyItems.push_back(item);
        } else {
            if (taskProcessingLevel == sf::PROCESSING_LEVEL_MIN) {
                historyItems.push_back(item);
            }
        }
    }

    sf::TaskState shownState = realTaskState;
    if (shownState == sf::TaskState::Deployed) {
        // do nothing
    } else {
        if (shownTaskResolved) {
            switch (shownTaskResolved->resolution.verdict()) {
                case sf::Verdict::Accepted:
                    shownState =  sf::TaskState::Accepted;
                    break;
                case sf::Verdict::Rejected:
                    shownState =  sf::TaskState::Rejected;
                    break;
            }
        }
    }

    return SubstitutionData{
        std::move(historyItems),
        std::move(shownTaskResolved),
        shownState,
        sf::PROCESSING_LEVEL_MIN};
}

} // namespace maps::wiki::socialsrv::serialize
