#include <maps/wikimap/mapspro/libs/editor_client/impl/parser.h>

#include "magic_strings.h"

#include <maps/libs/json/include/value.h>
#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/log8/include/log8.h>

namespace maps::wiki::editor_client {
namespace {

std::string
parseValue(const maps::json::Value& value)
{
    if (value.isBool()) {
        return
            value.as<bool>()
            ? tds::TRUE
            : tds::FALSE;
    } else {
        return value.toString();
    }
}

std::pair<std::string, std::string>
parsePlainAttribute(const std::string& fieldName, const maps::json::Value& value)
{
    REQUIRE(!value.isArray() &&
        !value.isObject(),
            "Expected single value field " << fieldName);
    return std::make_pair(fieldName, parseValue(value));
}

std::vector<std::unordered_map<std::string, std::string>>
parseTableAttribute(const maps::json::Value& rows)
{
    ASSERT(rows.isArray());
    std::vector<std::unordered_map<std::string, std::string>> parsed;
    parsed.reserve(rows.size());
    for (const auto& row : rows) {
        parsed.emplace_back();
        auto& rowData = parsed.back();
        for (const auto& fieldName : row.fields()) {
            rowData.insert(parsePlainAttribute(fieldName, row[fieldName]));
        }
    }
    return parsed;
}

std::vector<std::string>
parseMultiValueAttribute(const maps::json::Value& values)
{
    ASSERT(values.isArray());
    std::vector<std::string> parsed;
    parsed.reserve(values.size());
    for (const auto& value : values) {
        parsed.emplace_back(parseValue(value));
    }
    return parsed;
}

void
parseAttributes(BasicEditorObject& object, const maps::json::Value& attrs)
{
    ASSERT(attrs.isObject());
    for (const auto& fieldName : attrs.fields()) {
        const auto& attrValue = attrs[fieldName];
        if (attrs[fieldName].isArray()) {
            if (!attrs[fieldName].empty() && attrs[fieldName][0].isObject()) {
                object.tableAttributes.emplace(fieldName, parseTableAttribute(attrValue));
            } else {
                auto values = parseMultiValueAttribute(attrValue);
                if (!values.empty()) {
                    object.multiValueAttributes.emplace(fieldName, values);
                }
            }
        } else {
            object.plainAttributes.emplace(parsePlainAttribute(fieldName, attrValue));
        }
    }
}

BasicCommitData
parseCommitData(const maps::json::Value& commitJson)
{
    return
        {
            .id = boost::lexical_cast<revision::DBID>(
                commitJson[json::ID].as<std::string>()),
            .date = chrono::parseIsoDateTime(
                commitJson[json::DATE].as<std::string>()),
            .author = boost::lexical_cast<revision::UserID>(
                commitJson[json::UID].as<std::string>()),
        };
}

} // namespace

BasicEditorObject
parseObject(const maps::json::Value& objectJson)
{
    BasicEditorObject object;
    ASSERT(objectJson.hasField(json::ID) &&
        objectJson.hasField(json::CATEGORY_ID) &&
        objectJson.hasField(json::REVISION_ID) &&
        objectJson.hasField(json::STATE) &&
        objectJson.hasField(json::ATTRS));
    object.id = boost::lexical_cast<revision::DBID>(objectJson[json::ID].as<std::string>());
    object.revisionId = boost::lexical_cast<revision::RevisionID>(objectJson[json::REVISION_ID].as<std::string>());
    object.categoryId = objectJson[json::CATEGORY_ID].as<std::string>();
    if (objectJson.hasField(json::RECENT_COMMIT)) {
        object.recentCommit = parseCommitData(objectJson[json::RECENT_COMMIT]);
    }
    if (objectJson.hasField(json::FIRST_COMMIT)) {
        object.firstCommit = parseCommitData(objectJson[json::FIRST_COMMIT]);
    }
    if (objectJson.hasField(json::GEOMETRY)) {
        object.setGeometryInGeodetic(
            geolib3::readGeojson<geolib3::SimpleGeometryVariant>(objectJson[json::GEOMETRY]));
    }
    parseAttributes(object, objectJson[json::ATTRS]);
    object.deleted = (objectJson[json::STATE].as<std::string>() == json::DELETED);
    return object;
}

BasicEditorObject
parseJsonResponse(const std::string& jsonString)
{
    return parseObject(maps::json::Value::fromString(jsonString));
}

std::vector<ObjectIdentity>
parseObjectIdentities(const maps::json::Value& parsedJson)
{
    ASSERT(parsedJson.hasField(json::GEO_OBJECTS) && parsedJson[json::GEO_OBJECTS].isArray());
    std::vector<ObjectIdentity> objectIdentities;
    const auto& jsonObjects = parsedJson[json::GEO_OBJECTS];
    objectIdentities.reserve(jsonObjects.size());
    for (const auto& jsonObject : jsonObjects) {
        ASSERT(jsonObject.isObject() &&
            jsonObject.hasField(json::ID) &&
            jsonObject.hasField(json::CATEGORY_ID) &&
            jsonObject.hasField(json::REVISION_ID));
        objectIdentities.emplace_back(ObjectIdentity {
            boost::lexical_cast<revision::DBID>(jsonObject[json::ID].as<std::string>()),
            boost::lexical_cast<revision::RevisionID>(jsonObject[json::REVISION_ID].as<std::string>()),
            jsonObject[json::CATEGORY_ID].as<std::string>()
        });
    }
    return objectIdentities;
}

std::vector<ObjectIdentity>
parseObjectIdentities(const std::string& jsonString)
{
    return parseObjectIdentities(maps::json::Value::fromString(jsonString));
}

std::vector<BasicCommitData>
parseHistoryResponseJson(const std::string& jsonString)
{
    auto parsedJson = maps::json::Value::fromString(jsonString);
    if (!parsedJson.hasField(json::COMMITS)) {
        return {};
    }
    ASSERT(parsedJson[json::COMMITS].isArray());
    std::vector<BasicCommitData> history;
    for (const auto& jsonCommit : parsedJson[json::COMMITS]) {
        history.emplace_back(BasicCommitData {
            boost::lexical_cast<revision::DBID>(jsonCommit[json::ID].as<std::string>()),
            chrono::parseIsoDateTime(
                jsonCommit[json::DATE].as<std::string>()),
            boost::lexical_cast<revision::UserID>(
                jsonCommit[json::UID].as<std::string>())
        });
    }
    return history;
}

PoiConflicts
parsePoiConflictsResponseJson(const std::string& jsonString)
{
    auto parsedJson = maps::json::Value::fromString(jsonString);
    if (!parsedJson.hasField(json::CONFLICTS)) {
        return {};
    }
    ASSERT(parsedJson[json::CONFLICTS].isArray());
    PoiConflicts result;
    const auto allSeverities = enum_io::enumerateValues<poi_conflicts::ConflictSeverity>();
    for (const auto& jsonConflict : parsedJson[json::CONFLICTS]) {
        ASSERT(jsonConflict.hasField(json::ZOOM) && jsonConflict[json::ZOOM].isNumber());
        const auto zoom = jsonConflict[json::ZOOM].as<size_t>();
        for (const auto& severity : allSeverities) {
            const auto severityStr = toString(severity);
            if (!jsonConflict.hasField(severityStr)) {
                continue;
            }
            result.zoomToConflictingObjects[zoom].emplace(
                severity,
                parseObjectIdentities(jsonConflict[severityStr]));
        }
    }
    return result;
}

} // maps::wiki::editor_client
