#!/usr/bin/python3
import base64
import os
import sys
import urllib.parse
from datetime import datetime
from functools import cached_property

from jinja2 import Environment, FileSystemLoader, select_autoescape
from jinja2.runtime import Undefined


class MissingMacro(Undefined):
    """
    Output is piped straight into sendmail, so a macro Nagios did not export
    has to render empty rather than raise and turn the alert into an empty mail.
    Undefined already renders as empty; int() and float() are what raise.
    """

    def __int__(self):
        return 0

    def __float__(self):
        return 0.0


class NagiosNotify:
    template_dir = "/etc/nagios/templates"
    data_dir = "/usr/share/nagios"

    @cached_property
    def env(self):
        env = Environment(
            loader=FileSystemLoader(self.variables["TEMPLATEDIR"]),
            autoescape=select_autoescape(),
            undefined=MissingMacro
        )
        env.filters["timestamp_date"] = self.timestamp_date
        env.filters["duration"] = self.duration
        env.filters["encode_mime_header"] = self.encode_mime_header
        env.filters["urlencode"] = self.urlencode
        env.filters["base64"] = self.base64

        return env

    @property
    def default_variables(self):
        return {
            'DATADIR': self.data_dir,
            'TEMPLATEDIR': self.template_dir,
        }

    @cached_property
    def variables(self):
        env = dict(self.default_variables)
        for name, value in os.environ.items():
            if not name.startswith("NAGIOS_"):
                continue
            name = name[len("NAGIOS_"):]
            env[name] = value

        if "SERVICEINFOURL" in env:
            o = urllib.parse.urlparse(env["SERVICEINFOURL"])
            path = o.path.replace("extinfo.cgi", "cmd.cgi")
            env["SERVICECMDURL"] = f"{o.scheme}://{o.netloc}{path}?{o.query}"

        return env

    def get_template(self, filename: str):
        return self.env.get_template(f"{filename}.tmpl")

    def render(self, filename: str):
        tpl = self.get_template(filename)

        return tpl.render(self.variables)

    @staticmethod
    def timestamp_date(timestamp: str):
        try:
            timestamp = int(timestamp)
        except (TypeError, ValueError):
            timestamp = 0

        # nagios stores 0 for a state the object was never in, and a missing
        # macro lands here too; either way 1970-01-01 would be a lie
        if not timestamp:
            return "unknown"

        return datetime.fromtimestamp(timestamp).astimezone().strftime("%Y-%m-%d %H:%M:%S %Z")

    @staticmethod
    def duration(seconds):
        """
        Format a number of seconds the way Nagios formats $*DURATION$
        """
        # a passive result or a restart without retention can date the end of a
        # state before its start; report nothing rather than a negative duration
        days, seconds = divmod(max(0, int(seconds)), 86400)
        hours, seconds = divmod(seconds, 3600)
        minutes, seconds = divmod(seconds, 60)

        return f"{days}d {hours}h {minutes}m {seconds}s"

    @staticmethod
    def encode_mime_header(data: str, charset: str = "utf-8"):
        """
        Encode email header as rfc2047 (using base64 encoding)
        """
        encoded = base64.b64encode(data.encode(charset)).decode("ascii")

        return f"=?{charset}?b?{encoded}?="

    @staticmethod
    def urlencode(url: str):
        return urllib.parse.quote_plus(url)

    @staticmethod
    def base64(filename: str):
        with open(filename, 'rb') as f:
            encoded = base64.encodebytes(f.read()).decode("ascii")

        return encoded


if not len(sys.argv) > 1:
    print(f"Usage: {sys.executable} template_name")
    sys.exit(2)

template = sys.argv[1]
notify = NagiosNotify()
print(notify.render(template))
