#include "serialize.h"
#include <maps/wikimap/mapspro/services/social/src/libs/yacare/error.h>

#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/json_helpers.h>
#include <maps/wikimap/mapspro/libs/social_serv_serialize/include/jsonize_assessment.h>

#include <regex>


namespace maps::wiki::socialsrv {

namespace mws = social;
namespace mwa = assessment;

namespace {

const std::string TOKEN = "token";
const std::string GRADE = "grade";

const std::string ENTITY_ID = "entityId";
const std::string ENTITY_DOMAIN = "entityDomain";
const std::string ACTION_BY = "actionBy";
const std::string ACTION_AT = "actionAt";
const std::string ACTION = "action";
const std::string GRADE_VALUE = "gradeValue";
const std::string VALUE = "value";
const std::string COMMENT = "comment";

using common::readField;

template <typename T>
std::optional<T> readOptionalField(const json::Value& value, const std::string& fieldName)
{
    return value.hasField(fieldName)
        ? std::optional<T>(readField<T>(value, fieldName))
        : std::nullopt;
}

chrono::TimePoint readDateField(const json::Value& value, const std::string& fieldName)
{
    return chrono::parseIsoDateTime(readField<std::string>(value, fieldName));
}

void checkEntityId(const std::string& entityId)
{
    static const std::regex UINT_REGEX{"\\d+"};
    static const std::regex TICKET_REGEX{"[A-Z]+-\\d+"};

    REQUIRE(
        std::regex_match(entityId, UINT_REGEX) ||
        std::regex_match(entityId, TICKET_REGEX),
        "Entity ID '" << entityId << "' is not an unsigned number nor Tracker ticket."
    );
}

} // namespace

PostGradeRequest parsePostGradeRequest(const std::string& jsonString)
{
    constexpr size_t requiredFields = 6;
    const auto parsedJson = json::Value::fromString(jsonString);
    REQUIRE(parsedJson.isObject(),
            yacare::errors::BadRequest() << "Parsed json is not an Object");
    REQUIRE(parsedJson.fields().size() == requiredFields + parsedJson.hasField(COMMENT),
            yacare::errors::BadRequest() << "Missing or unexpected fields");

    const auto entityId = readField<std::string>(parsedJson, ENTITY_ID);
    checkEntityId(entityId);

    return {
        {
            entityId,
            readField<mwa::Entity::Domain>(parsedJson, ENTITY_DOMAIN)
        },
        {
            readField<std::string>(parsedJson, ACTION),
            readField<mws::TUid>(parsedJson, ACTION_BY),
            readDateField(parsedJson, ACTION_AT)
        },
        readField<mwa::Grade::Value>(parsedJson, parsedJson.hasField(VALUE) ? VALUE : GRADE_VALUE),
        readOptionalField<std::string>(parsedJson, COMMENT)
    };
}

std::string toJson(const PostGradeResult& result)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder builder) {
        builder[TOKEN] = result.token;
        builder[GRADE] = [&](json::ObjectBuilder builder) {
            serialize::jsonize(builder, result.grade);
        };
    };
    return builder.str();
}

std::string toJson(const TokenResult& result)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder builder) {
        builder[TOKEN] = result.token;
    };
    return builder.str();
}

std::string toJson(const mwa::Unit& unit)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder builder) {
        serialize::jsonize(builder, unit);
    };
    return builder.str();
}

std::string toJson(const mwa::GradedUnitVec& gradedUnits)
{
    json::Builder builder;
    builder << [&](json::ArrayBuilder builder) {
        serialize::jsonize(builder, gradedUnits);
    };
    return builder.str();
}

std::string toJson(const mwa::UnitFeed& unitFeed)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder builder) {
        serialize::jsonize(builder, unitFeed);
    };
    return builder.str();
}

std::string toJson(const mwa::SampleInfo& sampleInfo)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder builder) {
        serialize::jsonize(builder, sampleInfo);
    };
    return builder.str();
}

std::string toJson(const mwa::SampleFeed& sampleFeed)
{
    json::Builder builder;
    builder << [&](json::ObjectBuilder builder) {
        serialize::jsonize(builder, sampleFeed);
    };
    return builder.str();
}

} // namespace maps::wiki::socialsrv
