local mod = {}
local kumo = require 'kumo'
local utils = require 'policy-extras.policy_utils'

local function all_hosts_match_suffix(hosts, suffix)
  for _, host in ipairs(hosts) do
    if not utils.ends_with(host, suffix) then
      return false
    end
  end
  return true
end

local function compute_ip_rollup_mx_list(domain, routing_domain)
  if routing_domain == nil then
    return nil
  end
  if not utils.ends_with(routing_domain, '.ip_rollup') then
    return nil
  end

  local mx = kumo.dns.lookup_mx(domain)

  local lowest_pref = nil
  local hosts = nil

  for pref, pref_hosts in pairs(mx.by_pref) do
    pref = tonumber(pref)
    if lowest_pref == nil or pref < lowest_pref then
      lowest_pref = pref
      hosts = pref_hosts
    end
  end

  local addrs = {}
  -- The host names are pre-sorted by lookup_mx
  for _, host in ipairs(hosts) do
    local host_addrs = kumo.dns.lookup_addr(host)
    -- The addresses are in an unspecified order.
    -- Sort them so that we produce a consistent list
    -- and hash consistently with the resulting ready queue name
    table.sort(host_addrs)
    for _, a in ipairs(host_addrs) do
      table.insert(addrs, { name = host, addr = a })
    end
  end

  return addrs
end

function mod.apply_ip_rollup_to_queue_config(domain, routing_domain, params)
  local mx_list = compute_ip_rollup_mx_list(domain, routing_domain)
  if mx_list then
    params.protocol = {
      smtp = {
        mx_list = mx_list,
      },
    }
  end
end

local function compute_ip_rollup(domain, mapping)
  local mx = kumo.dns.lookup_mx(domain)

  local lowest_pref = nil
  local hosts = nil

  -- Extract the lowest pref value (highest priority) set
  -- of mx hosts from the mx records
  for pref, pref_hosts in pairs(mx.by_pref) do
    pref = tonumber(pref)
    if lowest_pref == nil or pref < lowest_pref then
      lowest_pref = pref
      hosts = pref_hosts
    end
  end

  -- Find a matching mapping entry
  local routing_domain = nil
  for suffix, routing_domain in pairs(mapping) do
    if all_hosts_match_suffix(hosts, suffix) then
      return routing_domain
    end
  end
  return nil
end

--[[
This function performs ip-based rollup for domains whose
MX records match certain suffixies.

The mapping parameter must be a map of mx hostname suffix
to a synthetic routing domain name, which must also end
with `.ip_rollup`.

The recipient domain is resolved for the supplied message,
and its lowest preferenced (eg: highest priority) mx records
are compared against the hostname suffix.  If they all match,
then the message has its routing domain set to the routing
domain.

In order to use this successfully, you must also arrange
for your get_queue_config event to update the queue parameters:

```lua
kumo.on('get_queue_config', function(domain, tenant, campaign, routing_domain)
  local params = {}
  rollup.apply_ip_rollup_to_queue_config(domain, routing_domain, params
  return kumo.make_queue_config(params)
end)

kumo.on('smtp_server_message_received', function(msg)
  rollup.reroute_using_ip_rollup(msg, {
    ['.mail.protection.outlook.com.'] = 'outlook.ip_rollup',
  })
end)
```

To apply shaping to the result, if you are using the shaping helper,
you will need to use the ip rollup routing domain:

```toml
["outlook.ip_rollup"]
mx_rollup = false
# shaping parameters here
```

]]
function mod.reroute_using_ip_rollup(msg, mapping)
  local status, err = pcall(function()
    local recip_domain = msg:recipient_list()[1].domain
    local routing_domain = compute_ip_rollup(recip_domain, mapping)
    msg:set_meta('routing_domain', routing_domain)
  end)
  if not status then
    -- Log, but otherwise ignore failure to resolve the domain
    print(string.format('while checking ip_rollup: %s', err))
  end
end

--[[
This function considers a mapping of MX hostname suffixes
to equivalent routing domains.

The MX for the recipient domain is resolved, and the set
of MX host names are checked against the suffixes.
If every hostname matches a suffix in the mapping, it is
consider to be an overall match, and the `routing_domain`
meta value is set to the corresponding domain.

This causes the mail for the recipients to have separate
scheduled queues but to use the same ready_queue and
site_name when it comes to delivering the mail, allowing
you to enforce traffic shaping rules on the same entity.

To use, place something like this in your
`smtp_server_message_received` and/or
`http_message_received` event handlers:

rollup.reroute_based_on_mx_host_suffix(msg, {
  [".olc.protection.outlook.com."] = "outlook.com",
})

]]
function mod.reroute_based_on_mx_host_suffix(msg, mapping)
  local status, err = pcall(function()
    local recip_domain = msg:recipient_list()[1].domain
    local mx = kumo.dns.lookup_mx(recip_domain)
    for suffix, routing_domain in pairs(mapping) do
      if all_hosts_match_suffix(mx.hosts, suffix) then
        if recip_domain ~= routing_domain then
          msg:set_meta('routing_domain', routing_domain)
        end
        return
      end
    end
  end)
  if not status then
    -- Log, but otherwise ignore failure to resolve the domain
    print(string.format('while checking mx host pattern: %s', err))
  end
end

return mod
