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

namespace maps::wiki::social::feedback {

namespace {

TaskForUpdate setTaskStatusBySample(
    GatewayRW& gatewayRw,
    TUid uid,
    const TaskForUpdate& sampleTask,
    const TaskForUpdate& task)
{
    TaskPatch patch(uid);
    bool doUpdate = false;

    if (sampleTask.revealed() && !task.revealed()) {
        patch.setBucket(sampleTask.bucket());
        doUpdate = true;
    }
    if (sampleTask.resolved() && !task.resolved()) {
        patch.setResolution(sampleTask.resolved()->resolution);
        doUpdate = true;
    }
    // Some duplicates can be Deployed (it is terminal state)
    // and some duplicates can be Rejected (it is terminal state too).
    if (sampleTask.deployedAt() && !task.deployedAt()
        && task.resolved()->resolution.verdict() == Verdict::Accepted) {
        patch.setDeployedAt(sampleTask.deployedAt().value());
        doUpdate = true;
    }

    if (doUpdate) {
        return gatewayRw.updateTask(task, patch);
    } else {
        return task;
    }
}

enum class StatusLevel {
    STATUS_CREATED = 0,
    STATUS_REVEALED = 1,
    STATUS_ACCEPTED = 2,
    STATUS_TERMINAL = 3
};

StatusLevel statusLevel(const Task& task)
{
    if (task.deployedAt()) {
        return StatusLevel::STATUS_TERMINAL;
    }
    if (task.resolved()) {
        if (task.resolved()->resolution.verdict() == Verdict::Rejected) {
            return StatusLevel::STATUS_TERMINAL;
        } else {
            return StatusLevel::STATUS_ACCEPTED;
        }
    }
    if (task.revealed()) {
        return StatusLevel::STATUS_REVEALED;
    }
    return StatusLevel::STATUS_CREATED;
}

bool taskStatusLess(const Task& lhs, const Task& rhs)
{
    auto lStatus = statusLevel(lhs);
    auto rStatus = statusLevel(rhs);
    if (lStatus != rStatus) {
        return lStatus < rStatus;
    } else {
        return lhs.createdAt() > rhs.createdAt();
    }
}

} // namespace

std::optional<TaskForUpdate> bindDuplicates(
    GatewayRW& gatewayRw,
    TUid uid,
    TIds ids)
{
    if (ids.empty()) {
        return std::nullopt;
    }

    // select all Tasks
    auto inputTasks = gatewayRw.tasksForUpdateByFilter(TaskFilter().ids(ids));

    TIds headIds{std::move(ids)};
    for (const auto& task : inputTasks) {
        if (task.duplicateHeadId()) {
            headIds.insert(*task.duplicateHeadId());
        }
    }
    auto headTasks = gatewayRw.tasksForUpdateByFilter(TaskFilter().ids(headIds));
    auto duplicateTasks = gatewayRw.tasksForUpdateByFilter(
        TaskFilter().duplicateHeadIds(std::move(headIds)));

    // gather all Tasks
    TasksForUpdate allTasks{std::move(inputTasks)};
    for (auto& task : duplicateTasks) {
        // some tasks might be presented twice in allTasks
        allTasks.emplace_back(std::move(task));
    }
    duplicateTasks.clear();
    for (auto& task : headTasks) {
        allTasks.emplace_back(std::move(task));
    }
    headTasks.clear();

    TaskForUpdate newHead = *std::min_element(
        std::begin(allTasks), std::end(allTasks), taskStatusLess);

    // update duplicatedHeadId for all tasks
    TIds duplicateTaskIds;
    for (const auto& task : allTasks) {
        if (task.id() != newHead.id()) {
            duplicateTaskIds.insert(task.id());
        }
    }

    gatewayRw.updateDuplicatedHeadId(uid, duplicateTaskIds, newHead.id());

    auto updateHeadResult = gatewayRw.updateTask(
        newHead,
        TaskPatch(uid).setDuplicateHeadId(std::nullopt));

    return updateHeadResult;
}

TaskForUpdate changeHeadStatusAccordingToDuplicates(
    GatewayRW& gatewayRw,
    TUid uid,
    const TaskForUpdate& headTask)
{
    auto duplicates = gatewayRw.tasksForUpdateByFilter(
        TaskFilter().duplicateHeadId(headTask.id()));
    if (!duplicates.empty()) {
        auto mostLeftStatusedDuplicate
            = std::min_element(std::begin(duplicates), std::end(duplicates),
                taskStatusLess);

        if (statusLevel(*mostLeftStatusedDuplicate) < StatusLevel::STATUS_TERMINAL) {
            return setTaskStatusBySample(
                gatewayRw, uid, *mostLeftStatusedDuplicate, headTask);
        }
    }
    return headTask;
}

void changeDuplicatesStatusesAccordingToHead(
    GatewayRW& gatewayRw,
    TUid uid,
    const TaskForUpdate& headTask)
{
    auto duplicates = gatewayRw.tasksForUpdateByFilter(
        TaskFilter().duplicateHeadId(headTask.id()));
    for (const auto& task : duplicates) {
        setTaskStatusBySample(gatewayRw, uid, headTask, task);
    }
}

} // namespace maps::wiki::social::feedback
