#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/hypothesis_attrs.h>

#include <maps/libs/geolib/include/conversion.h>

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

#include <maps/libs/ymapsdf/include/ft.h>

namespace maps::mrc::db::eye {

namespace {

const std::string OBJECT_ID = "object_id";
const std::string COMMIT_ID = "commit_id";
const std::string NUMBER = "number";
const std::string FEEDBACK_TYPE = "feedback_type";
const std::string CORRECT_SPEED_LIMIT = "correct_speed_limit";
const std::string CURRENT_SPEED_LIMIT = "current_speed_limit";
const std::string DIRECTION = "direction";
const std::string TRUCK = "truck";
const std::string SUPPOSED_DIRECTION = "supposed_direction";
const std::string CURRENT_DIRECTION = "current_direction";
const std::string MOVEMENT = "movement";
const std::string MERCATOR_PATH = "mercator_path";
const std::string GEOM_TYPE = "type";
const std::string GEOM_COORDINATES = "coordinates";
const std::string IS_TOLL = "is_toll";
const std::string CORRECT_FT_TYPE = "correct_ft_type";
const std::string CURRENT_FT_TYPE = "current_ft_type";
const std::string IS_AHEAD = "is_ahead";
const std::string HAS_BARRIER = "has_barrier";

json::Value serialize(const geolib3::Polyline2& polyline)
{
    std::vector<json::Value> coordinates;
    for (const auto& point: polyline.points()) {
        coordinates.emplace_back(
            std::vector{json::Value(point.x()), json::Value(point.y())}
        );
    }
    return json::Value {
        {GEOM_TYPE, json::Value{"LineString"}},
        {GEOM_COORDINATES, json::Value{coordinates}},
    };
}

json::Value serialize(ymapsdf::ft::Type ftType)
{
    using IntType = std::underlying_type<ymapsdf::ft::Type>::type;
    return json::Value{static_cast<IntType>(ftType)};
}

} // namespace

AbsentTrafficLightAttrs AbsentTrafficLightAttrs::fromJson(const json::Value& value) {
    AbsentTrafficLightAttrs attrs;
    if (value.hasField(OBJECT_ID) && value.hasField(COMMIT_ID)) {
        attrs.junctionRevisionId = {
            value[OBJECT_ID].as<wiki::revision::DBID>(),
            value[COMMIT_ID].as<wiki::revision::DBID>()
        };
    }

    return attrs;
}

json::Value toJson(const AbsentTrafficLightAttrs& attrs) {
    if (!attrs.junctionRevisionId.empty()) {
        return json::Value {
            {OBJECT_ID, json::Value(attrs.junctionRevisionId.objectId())},
            {COMMIT_ID, json::Value(attrs.junctionRevisionId.commitId())},
        };
    } else {
        return json::Value{json::repr::ObjectRepr{}};
    }
}


AbsentHouseNumberAttrs AbsentHouseNumberAttrs::fromJson(const json::Value& value) {
    return {value[NUMBER].as<std::string>()};
}

json::Value toJson(const AbsentHouseNumberAttrs& attrs) {
    return json::Value {
        {NUMBER, json::Value(attrs.number)},
    };
}


TrafficSignAttrs TrafficSignAttrs::fromJson(const json::Value& value) {
    wiki::social::feedback::Type type;
    fromString(value[FEEDBACK_TYPE].as<std::string>(), type);
    return {type};
}

json::Value toJson(const TrafficSignAttrs& attrs) {
    return json::Value {
        {FEEDBACK_TYPE, json::Value(std::string(toString(attrs.feedbackType)))},
    };
}


WrongSpeedLimitAttrs WrongSpeedLimitAttrs::fromJson(const json::Value& value) {
    WrongSpeedLimitAttrs attrs;
    attrs.roadElementRevisionId = {
        value[OBJECT_ID].as<wiki::revision::DBID>(),
        value[COMMIT_ID].as<wiki::revision::DBID>()
    };
    attrs.correctSpeedLimit = value[CORRECT_SPEED_LIMIT].as<int>();
    if (value.hasField(CURRENT_SPEED_LIMIT)) {
        attrs.currentSpeedLimit = value[CURRENT_SPEED_LIMIT].as<int>();
    } else {
        attrs.currentSpeedLimit = std::nullopt;
    }
    if (value.hasField(DIRECTION)) {
        attrs.direction = (ymapsdf::rd::Direction)value[DIRECTION].as<int>();
    } else {
        attrs.direction = std::nullopt;
    }
    if (value.hasField(TRUCK)) {
        attrs.truck = value[TRUCK].as<bool>();
    } else {
        attrs.truck = std::nullopt;
    }
    return attrs;
}

json::Value toJson(const WrongSpeedLimitAttrs& attrs) {
    REQUIRE(!attrs.roadElementRevisionId.empty(), "Wrong speed limit has empty revision id");

    json::repr::ObjectRepr content{
        {OBJECT_ID, json::Value(attrs.roadElementRevisionId.objectId())},
        {COMMIT_ID, json::Value(attrs.roadElementRevisionId.commitId())},
        {CORRECT_SPEED_LIMIT, json::Value(attrs.correctSpeedLimit)},
    };
    if (attrs.currentSpeedLimit.has_value()) {
        content.emplace(CURRENT_SPEED_LIMIT, json::Value{attrs.currentSpeedLimit.value()});
    }
    if (attrs.direction.has_value()) {
        content.emplace(DIRECTION, json::Value{(int)attrs.direction.value()});
    }
    if (attrs.truck.has_value()) {
        content.emplace(TRUCK, json::Value{attrs.truck.value()});
    }
    return json::Value(content);
}


WrongDirectionAttrs WrongDirectionAttrs::fromJson(const json::Value& value) {
    WrongDirectionAttrs attrs;
    fromString(value[SUPPOSED_DIRECTION].as<std::string>(), attrs.supposedDirection);
    fromString(value[CURRENT_DIRECTION].as<std::string>(), attrs.currentDirection);
    attrs.roadElementRevisionId = {
        value[OBJECT_ID].as<wiki::revision::DBID>(),
        value[COMMIT_ID].as<wiki::revision::DBID>()
    };

    return attrs;
}

json::Value toJson(const WrongDirectionAttrs& attrs) {
    REQUIRE(!attrs.roadElementRevisionId.empty(), "Wrong speed limit has empty revision id");

    return json::Value{
        {OBJECT_ID, json::Value(attrs.roadElementRevisionId.objectId())},
        {COMMIT_ID, json::Value(attrs.roadElementRevisionId.commitId())},
        {SUPPOSED_DIRECTION, json::Value(std::string(toString(attrs.supposedDirection)))},
        {CURRENT_DIRECTION, json::Value(std::string(toString(attrs.currentDirection)))},
    };
}


ProhibitedPathAttrs ProhibitedPathAttrs::fromJson(const json::Value& value) {
    ProhibitedPathAttrs attrs;
    fromString(value[MOVEMENT].as<std::string>(), attrs.movement);

    geolib3::PointsVector points;
    points.reserve(value[MERCATOR_PATH][GEOM_COORDINATES].size());
    for (const auto& pointJson : value[MERCATOR_PATH][GEOM_COORDINATES]) {
        points.emplace_back(pointJson[0].as<double>(), pointJson[1].as<double>());
    }
    attrs.mercatorPath = geolib3::Polyline2(points);

    return attrs;
}

json::Value toJson(const ProhibitedPathAttrs& attrs) {
    return json::Value{
        {MOVEMENT, json::Value(std::string(toString(attrs.movement)))},
        {MERCATOR_PATH, json::Value(serialize(attrs.mercatorPath))},
    };
}


AbsentParkingAttrs AbsentParkingAttrs::fromJson(const json::Value& value) {
    AbsentParkingAttrs attrs;
    if (value.hasField(OBJECT_ID) && value.hasField(COMMIT_ID)) {
        attrs.parkingRevisionId = {
            value[OBJECT_ID].as<wiki::revision::DBID>(),
            value[COMMIT_ID].as<wiki::revision::DBID>()
        };
    }
    attrs.isToll = value[IS_TOLL].as<bool>();
    return attrs;
}

json::Value toJson(const AbsentParkingAttrs& attrs) {
    json::repr::ObjectRepr content{};
    if (!attrs.parkingRevisionId.empty()) {
        content.emplace(OBJECT_ID, json::Value{attrs.parkingRevisionId.objectId()});
        content.emplace(COMMIT_ID, json::Value{attrs.parkingRevisionId.commitId()});
    }
    content.emplace(IS_TOLL, json::Value{attrs.isToll});
    return json::Value(content);
}


WrongParkingFtTypeAttrs WrongParkingFtTypeAttrs::fromJson(const json::Value& value) {
    using IntType = std::underlying_type<ymapsdf::ft::Type>::type;

    WrongParkingFtTypeAttrs attrs;
    attrs.correctFtType = static_cast<ymapsdf::ft::Type>(value[CORRECT_FT_TYPE].as<IntType>());
    attrs.currentFtType = static_cast<ymapsdf::ft::Type>(value[CURRENT_FT_TYPE].as<IntType>());
    attrs.parkingRevisionId = {
        value[OBJECT_ID].as<wiki::revision::DBID>(),
        value[COMMIT_ID].as<wiki::revision::DBID>()
    };

    return attrs;
}

json::Value toJson(const WrongParkingFtTypeAttrs& attrs) {
    REQUIRE(!attrs.parkingRevisionId.empty(), "Wrong parking ft type has empty revision id");

    return json::Value{
        {OBJECT_ID, json::Value(attrs.parkingRevisionId.objectId())},
        {COMMIT_ID, json::Value(attrs.parkingRevisionId.commitId())},
        {CORRECT_FT_TYPE, serialize(attrs.correctFtType)},
        {CURRENT_FT_TYPE, serialize(attrs.currentFtType)},
    };
}


LaneHypothesisAttrs LaneHypothesisAttrs::fromJson(const json::Value& value) {
    LaneHypothesisAttrs attrs;
    attrs.roadElementRevisionId = {
        value[OBJECT_ID].as<wiki::revision::DBID>(),
        value[COMMIT_ID].as<wiki::revision::DBID>()
    };

    return attrs;
}

json::Value toJson(const LaneHypothesisAttrs& attrs) {
    REQUIRE(!attrs.roadElementRevisionId.empty(), "Lane hypothesis has empty revision id");

    return json::Value{
        {OBJECT_ID, json::Value(attrs.roadElementRevisionId.objectId())},
        {COMMIT_ID, json::Value(attrs.roadElementRevisionId.commitId())},
    };
}


SpeedBumpAttrs SpeedBumpAttrs::fromJson(const json::Value& value) {
    SpeedBumpAttrs attrs;

    if (value.hasField(OBJECT_ID) && value.hasField(COMMIT_ID)) {
        attrs.objectRevisionId = {
            value[OBJECT_ID].as<wiki::revision::DBID>(),
            value[COMMIT_ID].as<wiki::revision::DBID>()
        };
    }

    attrs.isAhead = value[IS_AHEAD].as<bool>();

    return attrs;
}

json::Value toJson(const SpeedBumpAttrs& attrs) {
    json::repr::ObjectRepr content{};
    if (!attrs.objectRevisionId.empty()) {
        content.emplace(OBJECT_ID, json::Value{attrs.objectRevisionId.objectId()});
        content.emplace(COMMIT_ID, json::Value{attrs.objectRevisionId.commitId()});
    }
    content.emplace(IS_AHEAD, json::Value{attrs.isAhead});
    return json::Value(content);
}

RailwayCrossingAttrs RailwayCrossingAttrs::fromJson(const json::Value& value) {
    RailwayCrossingAttrs attrs;

    if (value.hasField(OBJECT_ID) && value.hasField(COMMIT_ID)) {
        attrs.objectRevisionId = {
            value[OBJECT_ID].as<wiki::revision::DBID>(),
            value[COMMIT_ID].as<wiki::revision::DBID>()
        };
    }

    attrs.hasBarrier = value[HAS_BARRIER].as<bool>();

    return attrs;
}

json::Value toJson(const RailwayCrossingAttrs& attrs) {
    json::repr::ObjectRepr content{};
    if (!attrs.objectRevisionId.empty()) {
        content.emplace(OBJECT_ID, json::Value{attrs.objectRevisionId.objectId()});
        content.emplace(COMMIT_ID, json::Value{attrs.objectRevisionId.commitId()});
    }
    content.emplace(HAS_BARRIER, json::Value{attrs.hasBarrier});
    return json::Value(content);
}

} // namespace maps::mrc::db::eye
