BareGit
#include "email_sender_mailjet.h"

#include <chrono>
#include <cstddef>
#include <memory>
#include <span>
#include <string>
#include <utility>
#include <vector>

#include <nlohmann/json.hpp>
#include <mw/http_client.hpp>
#include <mw/utils.hpp>

namespace
{

constexpr std::string_view MAILJET_SEND_URL =
    "https://api.mailjet.com/v3.1/send";

std::string htmlEscape(const std::string& input)
{
    std::string result;
    result.reserve(input.size());
    for(char value : input)
    {
        switch(value)
        {
        case '&':
            result += "&amp;";
            break;
        case '<':
            result += "&lt;";
            break;
        case '>':
            result += "&gt;";
            break;
        case '"':
            result += "&quot;";
            break;
        case '\'':
            result += "&#39;";
            break;
        default:
            result.push_back(value);
        }
    }
    return result;
}

std::string basicAuthorization(
    const std::string& api_key, const std::string& secret_key)
{
    const std::string credentials = api_key + ':' + secret_key;
    std::vector<unsigned char> bytes(
        credentials.begin(), credentials.end());
    return "Basic " + mw::base64Encode(
        std::span<unsigned char>(bytes), false, true);
}

} // namespace

MailjetEmailSender::MailjetEmailSender(
    std::unique_ptr<mw::HTTPSessionInterface> session,
    std::string from_address,
    std::string from_name,
    std::string api_key,
    std::string secret_key)
    : session_(std::move(session)),
      from_address_(std::move(from_address)),
      from_name_(std::move(from_name)),
      api_key_(std::move(api_key)),
      secret_key_(std::move(secret_key))
{}

mw::E<void> MailjetEmailSender::configure()
{
    auto protocols = session_->allowedProtocols("https");
    if(!protocols)
    {
        return std::unexpected(std::move(protocols.error()));
    }
    auto redirect_protocols = session_->allowedRedirectProtocols("https");
    if(!redirect_protocols)
    {
        return std::unexpected(std::move(redirect_protocols.error()));
    }
    session_->followRedirects(false);
    auto redirections = session_->maxRedirections(0);
    if(!redirections)
    {
        return std::unexpected(std::move(redirections.error()));
    }
    auto maximum = session_->maxSize(64 * 1024);
    if(!maximum)
    {
        return std::unexpected(std::move(maximum.error()));
    }
    auto connection_timeout = session_->connectionTimeout(
        std::chrono::seconds(5));
    if(!connection_timeout)
    {
        return std::unexpected(std::move(connection_timeout.error()));
    }
    return session_->transferTimeout(std::chrono::seconds(15));
}

mw::E<void> MailjetEmailSender::send(const AuthenticationEmail& email)
{
    const std::string link = email.confirmation_url.str();
    const std::string text =
        "Use this link to sign in to Card Collection. The link expires in "
        "ten minutes:\n\n" + link +
        "\n\nIf you did not request this message, ignore it.";
    const std::string html =
        "<p>Use this link to sign in to Card Collection. The link expires "
        "in ten minutes:</p><p><a href=\"" + htmlEscape(link) +
        "\">Sign in</a></p><p>If you did not request this message, "
        "ignore it.</p>";
    nlohmann::json body = {
        {"Messages", nlohmann::json::array({
            {
                {"From", {
                    {"Email", from_address_},
                    {"Name", from_name_},
                }},
                {"To", nlohmann::json::array({{
                    {"Email", email.recipient},
                }})},
                {"Subject", "Sign in to Card Collection"},
                {"TextPart", text},
                {"HTMLPart", html},
            },
        })},
    };
    mw::HTTPRequest request(MAILJET_SEND_URL);
    request.setContentType("application/json");
    request.setPayload(body.dump());
    request.addHeader(
        "Authorization", basicAuthorization(api_key_, secret_key_));
    auto response = session_->post(request);
    if(!response)
    {
        return std::unexpected(mw::runtimeError(
            "Mailjet request failed"));
    }
    if((**response).status < 200 || (**response).status >= 300)
    {
        return std::unexpected(mw::runtimeError(
            "Mailjet rejected the authentication email"));
    }
    try
    {
        const nlohmann::json parsed = nlohmann::json::parse(
            (**response).payloadAsStr());
        if(!parsed.contains("Messages") ||
           !parsed["Messages"].is_array() ||
           parsed["Messages"].size() != 1 ||
           parsed["Messages"][0].value("Status", "") != "success")
        {
            return std::unexpected(mw::runtimeError(
                "Mailjet returned an unsuccessful message status"));
        }
    }
    catch(const nlohmann::json::exception&)
    {
        return std::unexpected(mw::runtimeError(
            "Mailjet returned an invalid response"));
    }
    return {};
}

bool MailjetEmailSender::usesGlobalQuota() const
{
    return true;
}