#include <maps/wikimap/mapspro/services/social/src/libs/profile/profile_ui.h>
#include <maps/wikimap/mapspro/services/social/src/api/globals.h>
#include <maps/wikimap/mapspro/services/social/src/libs/common/common.h>
#include <maps/wikimap/mapspro/services/social/src/libs/yacare/helpers.h>
#include <maps/wikimap/mapspro/libs/notifications/include/mail_event.h>

#include <aws/sqs/model/SendMessageRequest.h>
#include <yandex/maps/wiki/social/profile_gateway.h>
#include <yandex/maps/wiki/social/profile.h>
#include <yandex/maps/wiki/social/prompt_gateway.h>
#include <yandex/maps/wiki/social/prompt.h>
#include <maps/libs/json/include/value.h>
#include <maps/libs/locale/include/convert.h>
#include <maps/libs/log8/include/log8.h>

#include <pqxx/pqxx>

namespace maps::wiki::socialsrv {

namespace {

const std::string UID = "uid";
const std::string PROFILE = "profile";
const std::string ABOUT = "about";
const std::string EMAIL = "email";
const std::string BROADCAST_SUBSCRIPTION = "broadcastSubscription";
const std::string NEWS_SUBSCRIPTION = "newsSubscription";
const std::string NEWS_SUBSCRIPTION_PROMPT_PROCESSED = "newsSubscriptionPromptProcessed";
const std::string LOCALE = "locale";
const std::string TOKEN = "token";

const size_t MAX_ABOUT_SIZE = 2000;
const size_t MAX_EMAIL_SIZE = 100;

social::ProfileOptionalFields optionalProfileFieldsFromJson(const json::Value& value)
{
    social::ProfileOptionalFields result;

    if (value.hasField(ABOUT)) {
        const auto& field = value[ABOUT];
        REQUIRE(
            field.isString(),
            yacare::errors::BadRequest() << "Invalid '" << ABOUT << "' value"
        );

        result.about = field.as<std::string>();
        SOCIAL_REQUIRE(
            result.about->size() <= MAX_ABOUT_SIZE,
            ContentTooLarge, "Too large '" << ABOUT << "' value"
        );
    }
    if (value.hasField(EMAIL)) {
        const auto& field = value[EMAIL];
        REQUIRE(
            field.isString(),
            yacare::errors::BadRequest() << "Invalid '" << EMAIL << "' value"
        );

        result.email = field.as<std::string>();
        SOCIAL_REQUIRE(
            result.email->size() <= MAX_EMAIL_SIZE,
            ContentTooLarge, "Too large '" << EMAIL << "' value"
        );
    }
    if (value.hasField(BROADCAST_SUBSCRIPTION)) {
        const auto& field = value[BROADCAST_SUBSCRIPTION];
        REQUIRE(
            field.isBool(),
            yacare::errors::BadRequest()
                << "Invalid '" << BROADCAST_SUBSCRIPTION << "' value"
        );

        result.hasBroadcastSubscription = field.as<bool>();
    }
    if (value.hasField(NEWS_SUBSCRIPTION)) {
        const auto& field = value[NEWS_SUBSCRIPTION];
        REQUIRE(
            field.isBool(),
            yacare::errors::BadRequest()
                << "Invalid '" << NEWS_SUBSCRIPTION << "' value"
        );

        result.hasNewsSubscription = field.as<bool>();
    }
    if (value.hasField(LOCALE)) {
        const auto& field = value[LOCALE];
        REQUIRE(
            field.isString(),
            yacare::errors::BadRequest() << "Invalid '" << LOCALE << "' value"
        );

        result.locale = field.as<std::string>();
        try {
            locale::to<locale::Locale>(*result.locale);
        } catch (const locale::LocaleParsingError&) {
            throw yacare::errors::BadRequest()
                << "Invalid '" << LOCALE << "' value";
        }
    }

    return result;
}

bool extractPromptProcessedFromJson(const json::Value& value)
{
    if (value.hasField(NEWS_SUBSCRIPTION_PROMPT_PROCESSED)) {
        const auto& field = value[NEWS_SUBSCRIPTION_PROMPT_PROCESSED];
        REQUIRE(
            field.isBool(),
            yacare::errors::BadRequest()
                << "Invalid '" << NEWS_SUBSCRIPTION_PROMPT_PROCESSED << "' value"
        );
        return field.as<bool>();
    }
    return false;
}

bool newsSubscriptionMailShouldBeSent(
    const std::optional<social::Profile>& profile,
    const social::ProfileOptionalFields& newProfileFields)
{
    bool mailWasSent = profile && profile->isNewsSubscriptionWelcomeMailSent();
    bool hasNewsSubscription = profile && profile->hasNewsSubscription();

    return
        mailWasSent == false &&
        hasNewsSubscription == false &&
        newProfileFields.hasNewsSubscription == true;
}

yacare::ThreadPool profileThreadPool("profileThreadPool", 1, 1024);

} // anonymous namespace

YCR_USE(profileThreadPool) {

YCR_RESPOND_TO("GET /profile/$", token = "")
{
    auto userId = positionalParam<UserId>(argv, 0);

    auto socialTxn = Globals::dbPools().socialReadTxn(token);
    social::ProfileGateway profileGtw(*socialTxn);
    social::PromptGateway promptGtw(*socialTxn);

    auto profile = profileGtw.getUserProfile(userId);

    auto prompts = promptGtw.load(
        social::table::PromptTbl::uid == userId &&
        social::table::PromptTbl::type == social::PromptType::NewsSubscription
    );

    ProfileUI profileUI(profile, prompts);

    response << YCR_JSON(respBuilder) {
        respBuilder[UID] = std::to_string(userId);
        respBuilder[PROFILE] << [&](json::ObjectBuilder builder) {
            json(profileUI, builder);
        };
    };
}

YCR_RESPOND_TO("POST /profile/$")
{
    auto userId = positionalParam<UserId>(argv, 0);
    auto body = json::Value::fromString(request.body());
    auto profileOptFields = optionalProfileFieldsFromJson(body);
    auto promptProcessed = extractPromptProcessedFromJson(body);

    auto writeContext = Globals::dbPools().writeContext();
    auto& socialTxn = writeContext.socialTxn();

    // Update Profile
    //
    social::ProfileGateway profileGtw(socialTxn);

    auto profile = profileGtw.getUserProfile(userId);

    bool mailShouldBeSent = newsSubscriptionMailShouldBeSent(
        profile,
        profileOptFields
    );

    if (mailShouldBeSent) {
        profileOptFields.newsSubscriptionWelcomeMailSent = true;
    }

    std::optional<social::Profile> updatedProfile =
        profileOptFields.empty()
            ? profile
            : profileGtw.upsertProfile(userId, profileOptFields);

    // Update Prompts
    //
    social::PromptGateway promptGtw(socialTxn);

    if (promptProcessed) {
        social::Prompt newPrompt;

        newPrompt.uid = userId;
        newPrompt.type = social::PromptType::NewsSubscription;
        newPrompt.processedAt = chrono::TimePoint::clock::now();

        promptGtw.insert(newPrompt);
    }

    auto allPrompts = promptGtw.load(
        social::table::PromptTbl::uid == userId &&
        social::table::PromptTbl::type == social::PromptType::NewsSubscription
    );

    auto token = writeContext.commit();

    if (mailShouldBeSent) {
        notifications::MailEventSender sender(
            "notifications", Globals::sqsConfig(), Globals::sqsClient()
        );

        notifications::MailEvent mailEvent(
            userId, notifications::MailType::NewsSubscriptionThanks);
        if (!sender.send(mailEvent)) {
            YCR_LOG_METRIC("notification-queue-write-errors", 1);
        }
    }

    // Form Response
    //
    ProfileUI profileUI(updatedProfile, allPrompts);

    response << YCR_JSON(respBuilder) {
        respBuilder[TOKEN] = token;
        respBuilder[UID] = std::to_string(userId);
        respBuilder[PROFILE] << [&](json::ObjectBuilder builder) {
            json(profileUI, builder);
        };
    };
}

} // YCR_USE

} // namespace maps::wiki::socialsrv
