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

#include "helpers.h"
#include <maps/infra/yacare/include/helpers.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/http/include/http.h>
#include <maps/libs/json/include/exception.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/enum_io/include/enum_io.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <yandex/maps/wiki/social/exception.h>
#include <yandex/maps/wiki/social/feedback/review.h>

namespace maps::wiki::socialsrv {

namespace {

const std::string ERROR = "error";
const std::string MESSAGE = "message";
const std::string STATUS = "status";

const auto HTTP_OK = static_cast<int>(http::Status::OK);
const auto HTTP_INTERNAL_SERVER_ERROR = static_cast<int>(http::Status::InternalServerError);
const std::string INTERNAL_ERROR = "INTERNAL_ERROR";
const std::string ERR_BAD_REQUEST = "ERR_BAD_REQUEST";
const std::string ERR_USER_NOT_EXISTS = "ERR_USER_NOT_EXISTS";

const std::string ERR_FEEBACK_REVIEW_NOT_EXISTS = "ERR_USER_NOT_EXISTS";
const std::string ERR_INVALID_OPERATION = "ERR_INVALID_OPERATION";

std::string
formatJsonError(
    std::string_view status,
    std::string_view message)
{
    json::Builder builder;
    builder << YCR_JSON(respBuilder) {
        respBuilder[ERROR] << YCR_JSON(errorBuilder) {
            errorBuilder[STATUS] = status;
            errorBuilder[MESSAGE] = message;
        };
    };
    return builder.str();
}

void putErrorToResponse(
    yacare::Response& response,
    std::string_view status,
    std::string_view message)
{
    makeJsonResponse(response, formatJsonError(status, message));
}

controller::ExceptionInfo
createExceptionInfo(
    std::string_view status,
    std::string_view message,
    controller::IsInternalError isInternalError = controller::IsInternalError::No)
{
    return controller::ExceptionInfo(
        std::string(message),
        formatJsonError(status, message),
        isInternalError
    );
}

} // namespace

const maps::enum_io::Representations<Error::Status> ERROR_STATUS_ENUM_REPRESENTATION {
    {Error::Status::AssessmentLimitExceeded, "ERR_ASSESSMENT_LIMIT_EXCEEDED"},
    {Error::Status::ContentTooLarge, "ERR_CONTENT_TOO_LARGE"},
    {Error::Status::Duplicate, "ERR_DUPLICATE"},
    {Error::Status::FeedbackResolveLimitExceeded, "ERR_FEEDBACK_RESOLVE_LIMIT_EXCEEDED"},
    {Error::Status::Forbidden, "ERR_FORBIDDEN"},
    {Error::Status::InvalidOperation, ERR_INVALID_OPERATION},
    {Error::Status::NotFound, "ERR_NOT_FOUND"},
    {Error::Status::PresetDuplicate, "ERR_PRESET_DUPLICATE"},
    {Error::Status::PresetNotFound, "ERR_PRESET_NOT_FOUND"},
    {Error::Status::PresetsLimitExceed, "ERR_PRESETS_LIMIT_EXCEEDED"},
};

DEFINE_ENUM_IO(Error::Status, ERROR_STATUS_ENUM_REPRESENTATION);

Error::Error(Status status)
    //We don't use this HTTP code anyhow,
    //returning HTTP 200 even in case of server/client error
    : yacare::Error(HTTP_OK, {})
    , status_(status)
{ }

void Error::format(yacare::Response& response) const
{
    putErrorToResponse(response, toString(status_), what());
}

controller::ExceptionInfo
handleException()
{
    try {
        throw;
    } catch (const maps::wiki::social::feedback::ReviewDoesntExist& ex) {
        return createExceptionInfo(ERR_FEEBACK_REVIEW_NOT_EXISTS, ex.what()); // HTTP 200
    } catch (const maps::wiki::social::feedback::InvalidReviewOperation& ex) {
        return createExceptionInfo(ERR_INVALID_OPERATION, ex.what()); // HTTP 200
    } catch (const social::InvolvementNotFound& ex) {
        return createExceptionInfo(toString(Error::Status::NotFound), ex.what()); // HTTP 200
    } catch (const acl::AccessDenied& ex) {
        return createExceptionInfo(toString(Error::Status::Forbidden), ex.what()); // HTTP 200
    } catch (const acl::UserNotExists& ex) {
        return createExceptionInfo(ERR_USER_NOT_EXISTS, ex.what()); // HTTP 200
    } catch (const json::Error& ex) {
        ERROR() << "Json error : " << ex;
        return createExceptionInfo(ERR_BAD_REQUEST, ex.what()); // HTTP 200
    } catch (const Error& ex) {
        ERROR() << "Social error, status: " << ex.status() << " : " << ex;
        return createExceptionInfo(toString(ex.status()), ex.what());
    } catch (const yacare::errors::BadRequest& ex) { // ex.status() == 400
        ERROR() << "Yacare bad request error: " << ex;
        return createExceptionInfo(ERR_BAD_REQUEST, ex.what()); // HTTP 200
    } catch (const yacare::errors::InternalError& ex) { // ex.status() == 500
        // Dump stacktrace because exception can be throw from app handlers
        ERROR() << "Yacare internal server error: " << ex;
        return createExceptionInfo(INTERNAL_ERROR, ex.what(), controller::IsInternalError::Yes); // HTTP 500
    } catch (const yacare::ServerError& ex) { // ex.status() > 500
        // Dump without stacktrace to decrease cpu & memory usage if service overloaded
        // Example: http-503 (service unavailable), 504 (gateway timeout), etc.
        ERROR() << "Yacare server error, http status: " << ex.status() << " : " << ex.what();
        return createExceptionInfo(INTERNAL_ERROR, ex.what(), controller::IsInternalError::Yes); // HTTP 500
    } catch (const yacare::Error& ex) {
        ERROR() << "Yacare error, http status: " << ex.status() << " : " << ex;
        return createExceptionInfo(INTERNAL_ERROR, ex.what(), controller::IsInternalError::Yes); // HTTP 500
    } catch (const maps::Exception& ex) {
        ERROR() << "Maps error : " << ex;
        return createExceptionInfo(INTERNAL_ERROR, ex.what(), controller::IsInternalError::Yes); // HTTP 500
    } catch (const std::exception& ex) {
        ERROR() << "Std error : " << ex.what();
        return createExceptionInfo(INTERNAL_ERROR, ex.what(), controller::IsInternalError::Yes); // HTTP 500
    }
}

void errorReporter(const yacare::Request&, yacare::Response& response)
{
    const auto exceptionInfo = handleException();
    if (exceptionInfo.isInternalError() == controller::IsInternalError::Yes) {
        response.setStatus(HTTP_INTERNAL_SERVER_ERROR);
    }
    makeJsonResponse(response, exceptionInfo.formattedMessage());
}

} // namespace maps::wiki::socialsrv
