#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/assignments/include/aggregate.h>

#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/toloka/include/states.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/tolokers_results.h>
#include <maps/wikimap/mapspro/services/autocart/pipeline/libs/storage/include/assessors_results.h>

#include <maps/libs/common/include/exception.h>

namespace maps::wiki::autocart::pipeline {

namespace {

static const std::string INPUT_VALUES = "inputValues";
static const std::string OUTPUT_VALUES = "outputValues";
static const std::string STATE = "state";
static const std::string USER_ID = "workerId";
static const std::string TASK_ID = "taskId";
static const std::string RESULT_ID = "result_id";
static const std::string HEIGHT = "height";
static const std::string FT_TYPE_ID = "ft_type_id";

TolokersResult
aggregateByMajority(const std::vector<Assignment>& assignments) {
    REQUIRE(!assignments.empty(), "Failed to aggregate empty vector");
    TolokersResult result;
    result.id = assignments.front().resultId;
    result.bld = assignments.front().bld;
    result.taskId = assignments.front().taskId;
    // aggregate states
    size_t yesCount = 0;
    size_t noCount = 0;
    for (const Assignment& assignment : assignments) {
        result.userStates.emplace_back(assignment.userId, assignment.state);
        if (TolokaState::Yes == assignment.state) {
            yesCount++;
        } else if (TolokaState::No == assignment.state) {
            noCount++;
        }
    }
    if (yesCount > result.userStates.size() / 2) {
        result.state = TolokaState::Yes;
    } else if (noCount > result.userStates.size() / 2) {
        result.state = TolokaState::No;
    } else {
        result.state = TolokaState::Unknown;
    }
    return result;
}

} // namespace

Assignment Assignment::deserializeFromJson(const json::Value& value) {
    Assignment assignment;
    const json::Value& inputValues = value[INPUT_VALUES];
    const json::Value& outputValues = value[OUTPUT_VALUES];

    assignment.resultId = inputValues[RESULT_ID].as<uint64_t>();
    assignment.bld = Building::fromJson(inputValues);
    assignment.userId = value[USER_ID].as<std::string>();
    assignment.taskId = value[TASK_ID].as<std::string>();
    fromString(outputValues[STATE].as<std::string>(), assignment.state);
    if (TolokaState::Yes == assignment.state) {
        if (outputValues.hasField(HEIGHT)) {
            int height = std::stoi(outputValues[HEIGHT].as<std::string>());
            assignment.bld.setHeight(height);
        }
        if (outputValues.hasField(FT_TYPE_ID)) {
            int code = std::stoi(outputValues[FT_TYPE_ID].as<std::string>());
            assignment.bld.setFTTypeId(decodeFTTypeId(code));
        }
    }
    return assignment;
}


std::vector<TolokersResult>
aggregateTolokersAssignments(const json::Value& assignmentsJson) {
    std::unordered_map<uint64_t, std::vector<Assignment>> resultIdToAssignments;
    for (const json::Value& assignmentJson : assignmentsJson) {
        Assignment assignment = Assignment::deserializeFromJson(assignmentJson);
        resultIdToAssignments[assignment.resultId].push_back(assignment);
    }
    std::vector<TolokersResult> results;
    results.reserve(resultIdToAssignments.size());
    for (const auto& [resultId, assignments] : resultIdToAssignments) {
        results.push_back(aggregateByMajority(assignments));
    }
    return results;
}

std::vector<AssessorsResult>
aggregateAssessorsAssignments(
    const json::Value& assignmentsJson,
    const std::map<std::string, std::string>& loginByWorkerId)
{
    std::vector<AssessorsResult> results(assignmentsJson.size());
    for (size_t i = 0; i < assignmentsJson.size(); i++) {
        Assignment assignment = Assignment::deserializeFromJson(assignmentsJson[i]);
        results[i].id = assignment.resultId;
        results[i].bld = assignment.bld;
        results[i].taskId = assignment.taskId;
        results[i].assessorId = assignment.userId;
        results[i].state = assignment.state;
        results[i].login = loginByWorkerId.at(assignment.userId);
    }
    return results;
}

} // namespace maps::wiki::autocart::pipeline
