#include "publish_feedback.h"
#include <maps/wikimap/mapspro/services/tasks_feedback/src/sprav_pedestrian_feedback/lib/parser.h>
#include <maps/wikimap/mapspro/services/tasks_feedback/src/sprav_pedestrian_feedback/lib/social.h>
#include <maps/wikimap/mapspro/services/tasks_feedback/src/sprav_pedestrian_feedback/lib/social_constants.h>

#include <maps/libs/http/include/http.h>
#include <maps/wikimap/mapspro/libs/http/http_utils.h>
#include <maps/libs/geolib/include/serialization.h>
#include <maps/libs/json/include/builder.h>
#include <maps/libs/enum_io/include/enum_io.h>

#include <boost/lexical_cast.hpp>
#include <chrono>
#include <sstream>

namespace maps::wiki::sprav_feedback {

namespace {

const std::string FEEDBACK_HANDLE = "/feedback/tasks";

constexpr enum_io::Representations<FeedbackReason>
    FEEDBACK_REASON_STRINGS {
    {FeedbackReason::EntryButNoData, "EntryButNoData"},
    {FeedbackReason::CommentForEntry, "CommentForEntry"},
    {FeedbackReason::CommentForNoEntry, "CommentForNoEntry"},
    {FeedbackReason::NoEntryButData, "NoEntryButData"},
    {FeedbackReason::SuspiciousName, "SuspiciousName"},
    {FeedbackReason::NoBuildingFound, "NoBuildingFound"},
    {FeedbackReason::DuplicateName, "DuplicateName"},
    {FeedbackReason::NameConflict, "NameConflict"}
};

std::string generateReasonString(
    FeedbackReason reason,
    const std::optional<std::string>& entranceName)
{
    std::stringstream retVal;
    switch (reason) {
        case FeedbackReason::NameConflict:
            ASSERT(entranceName);
            retVal << "В этой точке пешеход нашел подъезд: '" << entranceName.value() << "'. "
                << "Поблизости уже есть подъезд с другим именем.";
            break;

        case FeedbackReason::DuplicateName:
            ASSERT(entranceName);
            retVal << "В этой точке пешеход нашел подъезд: '" << entranceName.value() << "'. "
                << "В этом здании уже есть подъезд с таким именем.";
            break;

        case FeedbackReason::NoBuildingFound:
            ASSERT(entranceName);
            retVal << "В этой точке пешеход нашел подъезд: '" << entranceName.value() << "'. "
                << "Не найдено ни одного здания, к которому мог бы отностися этот подъезд.";
            break;

        case FeedbackReason::SuspiciousName:
            ASSERT(entranceName);
            retVal << "В этой точке пешеход нашел подъезд со странным именем: '"
                << entranceName.value() << "'. ";
            break;

        case FeedbackReason::CommentForEntry:
            retVal << "В этой точке пешеход нашел подъезды и оставил комментарий.";
            break;

        case FeedbackReason::CommentForNoEntry:
            retVal << "В этой точке пешеход не нашел подъезды и оставил комментарий.";
            break;

        case FeedbackReason::EntryButNoData:
            retVal << "Получены ошибочные данные от пешехода. "
                << "Пешеход отметил, что нашёл подъзды, "
                << "но ни одного подъезда в данных нет.";
            break;

        case FeedbackReason::NoEntryButData:
            retVal << "Получены ошибочные данные от пешехода. "
                << "Пешеход отметил, что не нашёл здесь подъезды, "
                << "но в данных есть точки подъездов.";
            break;
    }
    return retVal.str();
}

std::string generateDescription(
    const EntrancesData& data,
    FeedbackReason reason,
    const std::optional<std::string>& entranceName)
{
    std::stringstream descr;

    descr << generateReasonString(reason, entranceName) << "\n\n";

    if (data.comment) {
        descr << "Комментарий:\n\"" << *data.comment << "\"\n\n";
    }

    if (data.address) {
        descr << "Адрес:\n\"" << *data.address << "\"\n\n";
    }

    if (!data.photoUrls.empty()) {
        descr << "Фото:\n";
        int count = 1;
        for (const auto& url : data.photoUrls) {
            descr << std::to_string(count) + ") " << url << "\n";
            count++;
        }
    }

    return descr.str();
}

std::string postRequestBody(
    const geolib3::Point2& geoPoint,
    const EntrancesData& data,
    FeedbackReason reason,
    const std::optional<std::string>& entranceName)
{
    json::Builder bodyBuilder;
    bodyBuilder << [&](json::ObjectBuilder bodyBuilder) {
        bodyBuilder[jf::TYPE] = TYPE_ENTRANCE;
        bodyBuilder[jf::HIDDEN] = true;
        bodyBuilder[jf::SOURCE] = "sprav-pedestrian-entrances";
        bodyBuilder[jf::WORKFLOW] = WORKFLOW_FEEDBACK;
        bodyBuilder[jf::POSITION] = geolib3::geojson(geoPoint);
        bodyBuilder[jf::DESCR] = generateDescription(data, reason, entranceName);

        bodyBuilder[jf::USER_ATTRS] = [&](json::ObjectBuilder builder) {
            builder["spravTaskId"] = data.assignmentId;
            builder["submitTimeUnixMs"] = data.submitTimeUnixMs;
            builder["createdAtTimeUnixMs"] = data.createdAtTimeUnixMs;
            builder["spravUserId"] = data.spravUserId;
        };
    };

    return bodyBuilder.str();
}

} // unnamed namespace

DEFINE_ENUM_IO(FeedbackReason, FEEDBACK_REASON_STRINGS);

revision::DBID publishFeedback(
    const geolib3::Point2& geoPoint,
    const EntrancesData& data,
    const std::string& socialUrl,
    FeedbackReason reason,
    const std::optional<std::string>& entranceName)
{
    http::URL socialHttpUrl(socialUrl);
    socialHttpUrl.setPath(FEEDBACK_HANDLE);

    const size_t maxAttempts = 6;
    const std::chrono::seconds initialTimeout{1};
    const double timeoutBackoff = 2;

    auto retryPolicy = maps::common::RetryPolicy()
        .setTryNumber(maxAttempts)
        .setInitialCooldown(initialTimeout)
        .setCooldownBackoff(timeoutBackoff);

    http::Client httpClient;
    auto [response, status] = httpClient.post(
        socialHttpUrl,
        socialBackofficeHttpHeaders(),
        postRequestBody(geoPoint, data, reason, entranceName),
        retryPolicy);

    REQUIRE(status == http_status::OK,
        "Unable to post feedback; Response status: " << response);

    return getFeedbackIdFromResponse(response);
}

} // namespace maps::wiki::sprav_feedback
