-- Git Multimail Plugin
--
-- This plugin enables support for git-multimail on the server-side which
-- allows for the nice(r) reporting of changes by email.
--
-- Copyright 2017 Daniel Silverstone <dsilvers@digital-scurf.org>
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
-- 1. Redistributions of source code must retain the above copyright
--    notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright
--    notice, this list of conditions and the following disclaimer in the
--    documentation and/or other materials provided with the distribution.
-- 3. Neither the name of the author nor the names of their contributors
--    may be used to endorse or promote products derived from this software
--    without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-- ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-- SUCH DAMAGE.

local gitano = require "gitano"

local luxio = require "luxio"
local sp = require "luxio.subprocess"

local git_multimail_py = nil

do
   local args = {
      "git", "config", "--path", "gitano.multimail.location",
      stdout = sp.PIPE,
      cwd = luxio.getenv("HOME"),
   }
   local proc = sp.spawn_simple(args)
   git_multimail_py = proc.stdout:read("*l")
   local how, why = proc:wait()
   if how ~= "exit" then
      return -- don't register, something really went wrong
   end
   if why ~= 0 then
      git_multimail_py = nil
   end
end

local GLOBAL_CONFS = {
   environment = "generic",
   refFilterExclusionRegex = "^refs/gitano/",
   verbose = "true",
}

local function run_multimail(confbits, env, stdin)
   local args = {
      env = env,
      stdin = sp.PIPE,
      stdout = sp.PIPE,
      stderr = sp.PIPE,
      git_multimail_py,
   }
   for k, v in pairs(confbits) do
      args[#args+1] = "-c"
      args[#args+1] = "multimailhook." .. k .. "=" .. v
   end
   gitano.log.ddebug("About to spawn git_multimail.py as...")
   for _, v in ipairs(args) do
      gitano.log.ddebug("=> " .. v)
   end
   local proc = sp.spawn_simple(args)
   proc.stdin:write(stdin)
   proc.stdin:close()
   local outcontent = proc.stdout:read("*a")
   local errcontent = proc.stderr:read("*a")
   local how, why = proc:wait()
   local function show(what, pfx)
      if what == "" then return end
      if what:sub(-1) ~= "\n" then what = what .. "\n" end
      for line in what:gfind("([^\n]*)\n") do
         gitano.log.state(pfx .. ": " .. line)
      end
   end
   if outcontent ~= "" or errcontent ~= "" then
      gitano.log.state("Git Multimail says:")
      show(outcontent, "O")
      show(errcontent, "E")
   end
   if (how ~= "exit") or (why ~= 0) then
      gitano.log.error("git_multimail.py failed: " .. tostring(how) .. " " .. tostring(why))
   end
   gitano.log.ddebug("Done with multimail")
end

local function git_multimail_post_receive_hook(repo, updates)
   -- We don't get here at all if we don't have git_multimail.py's location
   -- as such, our first job is to request authorisation from the ACL
   local context = {
      user = "gitano/multimail",
      source = "plugin_multimail",
      operation = "multimail",
   }
   gitano.log.ddebug("Multimail: Asking " .. repo.name .. " if multimail is allowed...")
   local action, reason = repo:run_lace(context)
   gitano.log.ddebug(("Multimail: action=%q reason=%q"):format(action,reason))
   if action ~= "allow" then
      gitano.log.info("Multimail disallowed: " .. tostring(action) .. ": " .. tostring(reason))
      return "continue"
   end

   -- Okay, we're allowed to run, so let's build our config...
   local confbits = gitano.util.deep_copy(GLOBAL_CONFS)
   local conf = repo.config
   local username = luxio.getenv("GITANO_USER")
   local user = conf.users[username] or {
      real_name = "Anonymous",
      email_address = "postmaster@localhost.localdomain"
   }
   -- And we need to set 'from'
   confbits.from = user.real_name .. " <" .. user.email_address .. ">"

   local function layer_confbits(fromrepo, pfx)
      gitano.log.ddebug("Multimail: Attempting to layer from " .. tostring(fromrepo.name) .. " via prefix " .. pfx)
      -- Note this is currently an unstable API
      local from = fromrepo.project_config
      for key, value in from:each(pfx) do
         key = key:sub(#pfx+2,-1)
         gitano.log.ddebug(("Multimail: => setting %q to %q"):format(key,value))
         confbits[key] = tostring(value)
      end
   end
   local gitano_admin_repo = (
      gitano.repository.find(repo.config, "gitano-admin.git"))
   layer_confbits(gitano_admin_repo, "defaults.multimail")
   layer_confbits(repo, "multimail")
   layer_confbits(gitano_admin_repo, "override.multimail")

   -- Now we run a supple hook (if necessary) which will be able to adjust
   -- the config bits...
   if repo:uses_hook("multimail") then
      gitano.log.debug("Multimail: Configuring supple for hook")
      gitano.actions.set_supple_globals("multimail")
      gitano.log.info("Multimail: Running hook")
      local info = {
         username = username,
         source = "plugin_multimail",
         realname = user.real_name or "",
         email = user.email_address or "",
      }
      local ok, msg = gitano.supple.run_hook("multimail", repo, info, updates, confbits)
      if not ok then
         gitano.log.error("Multimail: Hook failed: " .. tostring(msg))
         gitano.log.error("Multimail: Aborting send")
         return
      end
      gitano.log.info("Multimail: Finished running hook, continuing...")
   end

   -- Next we clean up the config bits, by copying over only what we whitelist
   local unfiltered = confbits
   confbits = {}
   for _, key in ipairs(
      {
         "mailingList", "refchangeList", "announceList", "commitList",
         "from",
         "announceShortLog", "commitBrowseURL", "refchangeShowGraph",
         "refchangeShowLog", "administrator", "emailPrefix",
         "refFilterInclusionRegex", "refFilterExclusionRegex",
         "refFilterDoSendRegex", "refFilterDontSendRegex",
         "verbose", "stdout",
   }) do
      local val = unfiltered[key]
      if val and val ~= "" then
         gitano.log.ddebug(
            ("Multimail: Permitted %q => %q"):format(key, unfiltered[key]))
         confbits[key] = unfiltered[key]
      end
   end

   -- No matter what goes on in the clod configs, we need to set repoName
   confbits.repoName = repo.name

   local fullusername = user.real_name .. " (" .. username .. ")"
   local env = {
      USERNAME = fullusername,
      USER = fullusername,
   }

   -- Prepare the stdin for git_multimail.py, in post-receive format
   local stdin = {}
   for ref, chg in pairs(updates) do
      stdin[#stdin+1] = table.concat({chg.oldsha, chg.newsha, ref}, " ")
   end
   stdin = table.concat(stdin, "\n") .. "\n"

   -- Finally, if there's any address set at all, run multimail
   if (confbits.mailingList or
       confbits.announceList or
       confbits.commitList or
       confbits.refchangeList) then
      run_multimail(confbits, env, stdin)
   else
      gitano.log.info("Multimail: Did not run, no target addresses set")
   end

   return "continue"
end

if git_multimail_py ~= nil and luxio.getenv("GITANO_USER") ~= "gitano/bypass" then
   gitano.hooks.add(gitano.hooks.names.POST_RECEIVE,
                    100, git_multimail_post_receive_hook)
end
