--
-- (C) 2017-24 - ntop.org
--
local email = {
    name = "Email",
    endpoint_params = {
        {param_name = "smtp_server"}, {param_name = "email_sender"},
        {param_name = "smtp_port", optional = true},
        {param_name = "use_proxy", optional = true},
        {param_name = "smtp_username", optional = true},
        {param_name = "smtp_password", optional = true}
    },
    endpoint_template = {
        script_key = "email",
        template_name = "email_endpoint.template"
    },
    recipient_params = {
        {param_name = "email_recipient"}, {param_name = "cc", optional = true}
    },
    recipient_template = {
        script_key = "email",
        template_name = "email_recipient.template"
    }
}

require "lua_utils"
local json = require("dkjson")
local alert_utils = require "alert_utils"
local format_utils = require "format_utils"
local debug_endpoint = false

email.EXPORT_FREQUENCY = 60
email.prio = 200

local MAX_ALERTS_PER_EMAIL = 100
local MAX_NUM_SEND_ATTEMPTS = 5
local NUM_ATTEMPTS_KEY =
    "ntopng.alerts.modules_notifications_queue.email.num_attemps"

if ntop.getPref("ntopng.prefs.debug_email") == "1" then debug_endpoint = true end

-- ##############################################

-- @brief Returns the desided formatted output for recipient params
function email.format_recipient_params(recipient_params)
    return
        string.format("%s (%s)", recipient_params.email_recipient, email.name)
end

-- ##############################################

local function recipient2sendMessageSettings(recipient)
    local settings = {
        smtp_server = recipient.endpoint_conf.smtp_server,
        smtp_port = recipient.endpoint_conf.smtp_port,
        use_proxy = toboolean(recipient.endpoint_conf.use_proxy),
        from_addr = recipient.endpoint_conf.email_sender,
        to_addr = recipient.recipient_params.email_recipient,
        cc_addr = recipient.recipient_params.cc,
        username = recipient.endpoint_conf.smtp_username,
        password = recipient.endpoint_conf.smtp_password
    }

    return settings
end

-- ##############################################

local function buildMessageHeader(now_ts, from, to, cc, subject, body)
    local msg_id = "<" .. now_ts .. "." .. os.clock() .. "@ntopng>"

    local lines = {
        "From: " .. from, "To: " .. to, "Subject: " .. subject,
        "Date: " .. format_utils.formatEpochRFC2822(now_ts),
        "Message-ID: " .. msg_id, "Content-Type: text/html; charset=UTF-8"
    }

    if not isEmptyString(cc) then lines[#lines + 1] = "Cc: " .. cc end

    return table.concat(lines, "\r\n") .. "\r\n\r\n" .. body .. "\r\n"
end

-- ##############################################

function email.isAvailable() return (ntop.sendMail ~= nil) end

-- ##############################################

function email.sendEmail(subject, message_body, settings)
    local smtp_server = settings.smtp_server
    local smtp_port = settings.smtp_port
    local use_proxy = settings.use_proxy
    local from = settings.from_addr:gsub(".*<(.*)>", "%1")
    local to = settings.to_addr:gsub(".*<(.*)>", "%1")
    local cc = settings.cc_addr:gsub(".*<(.*)>", "%1")
    local product = ntop.getInfo(false).product
    local info = ntop.getHostInformation()

    if not isEmptyString(subject) then
        subject = subject .. " - "
    else
        subject = ""
    end
    subject = subject .. product .. " @ " .. info.instance_name .. " (" ..
                  info.ip .. ")"

    if not string.find(smtp_server, "://") then
        smtp_server = "smtp://" .. smtp_server
    end

    smtp_port = tonumber(smtp_port)
    if smtp_port and smtp_port > 0 then
        smtp_server = smtp_server .. ":" .. smtp_port
    end

    local parts = string.split(to, "@")

    if (parts) and (#parts == 2) then
        local sender_domain = parts[2]
        smtp_server = smtp_server .. "/" .. sender_domain
    end

    local message = buildMessageHeader(os.time(), settings.from_addr,
                                       settings.to_addr, settings.cc_addr,
                                       subject, message_body)

    -- Pass nil username and password when auth is not required
    local username = nil
    local password = nil
    if not isEmptyString(settings.username) then username = settings.username end
    if not isEmptyString(settings.password) then password = settings.password end

    return ntop.sendMail(from, to, cc, message, smtp_server, username, password,
                         use_proxy, debug_endpoint)
end

-- ##############################################

-- Dequeue alerts from a recipient queue for sending notifications
function email.dequeueRecipientAlerts(recipient, budget)
    local sent = 0
    local more_available = true
    local budget_used = 0

    local success = true
    local error_message = nil
    local delivered = 0
    local discarded = 0
    local failures = 0

    local settings = recipient2sendMessageSettings(recipient)

    -- Dequeue alerts up to budget x MAX_ALERTS_PER_EMAIL
    -- Note: in this case budget is the number of email to send
    while budget_used <= budget and more_available do
        -- Dequeue MAX_ALERTS_PER_EMAIL notifications

        local notifications = {}

        local i = 0
        local total_len = 0
        while i < MAX_ALERTS_PER_EMAIL do
            local notification = ntop.recipient_dequeue(recipient.recipient_id)

            if notification then

                if debug_endpoint then
                    tprint("Email: dequeued notification")
                    tprint(notification)
                end

                if alert_utils.filter_notification(notification,
                                                   recipient.recipient_id) then
                    local notif = json.decode(notification.alert)

                    if debug_endpoint then
                        tprint("Email: notification matched the filter")
                    end

                    notifications[#notifications + 1] = notif
                    i = i + 1

                    if not notif.score then
                        -- Not an alert (e.g. report), send out
                        goto send_out
                    end
                else
                    discarded = discarded + 1
                end
            else
                break
            end
        end

        ::send_out::

        if not notifications or #notifications == 0 then
            more_available = false
            break
        end

        -- Prepare email

        -- Subject
        local subject = format_utils.formatSubject(notifications)

        -- Body
        local messages = {}
        for _, notif in ipairs(notifications) do
            messages[#messages + 1] = format_utils.formatMessage(notif, {
                show_entity = true,
                nohtml = false,
                nolabelhtml = true
            })
        end
        local message_body = table.concat(messages, "<br>")

        if debug_endpoint then tprint(message_body) end

        -- Send email
        local rv = email.sendEmail(subject, message_body, settings)

        -- Handle retries on failure
        if not rv.success then
            local num_attemps =
                (tonumber(ntop.getCache(NUM_ATTEMPTS_KEY)) or 0) + 1

            if num_attemps >= MAX_NUM_SEND_ATTEMPTS then
                ntop.delCache(NUM_ATTEMPTS_KEY)
                success = false
                error_message = "Unable to send alerts by email"
                failures = failures + #notifications
                goto done
            else
                ntop.setCache(NUM_ATTEMPTS_KEY, tostring(num_attemps))
                goto done
            end

        else
            delivered = delivered + #notifications
            ntop.delCache(NUM_ATTEMPTS_KEY)
        end

        -- Remove the processed messages from the queue
        budget_used = budget_used + #notifications
        sent = sent + 1
    end

    ::done::
    return {
        success = success,
        error_message = error_message,
        delivered = delivered,
        discarded = discarded,
        failures = failures,
        more_available = more_available
    }
end

-- ##############################################

function email.runTest(recipient)
    local message_info

    local settings = recipient2sendMessageSettings(recipient)

    local sent = email.sendEmail("TEST MAIL", "Email notification is working",
                                 settings)

    if sent.success then
        message_info = i18n("prefs.email_sent_successfully")
    else
        message_info = i18n("prefs.email_send_error", {
            msg = sent.msg,
            url = "https://www.ntop.org/guides/ntopng/user_interface/shared/alerts/available_endpoints.html#email"
        })
    end

    return sent.success, message_info

end

-- ##############################################

return email
