#!/usr/bin/env python3
# HTML Email (LDAP group recipients)
# Bulk: yes
#
# Copyright (C) 2026 Kuhn & Rueß GmbH - License: GNU General Public License v2
"""
Wrapper around the built-in mail notification plug-in.

A recipient that is no mail address is looked up as an LDAP group in the LDAP
connections already configured in this site; the address stored at the group is
used instead. Everything else is left to the built-in plug-in.
"""
# pylint: disable=import-error

import io
import os
import sys

import ldap
import ldap.filter

from cmk.utils import password_store

CONFIG = "etc/check_mk/multisite.d/wato/user_connections.mk"

# The rule cannot use the key "from" of the built-in mail plug-in ("from" is no
# Python identifier), so it offers "sender" and we rename it back here.
SENDER = {
    "PARAMETER_SENDER_ADDRESS": "PARAMETER_FROM_ADDRESS",
    "PARAMETER_SENDER_DISPLAY_NAME": "PARAMETER_FROM_DISPLAY_NAME",
}


def ldap_connections():
    """
    The enabled LDAP connections of this site
    """
    # The file appends to the variable, so it has to exist beforehand
    variables = {"user_connections": []}
    path = os.path.join(os.environ["OMD_ROOT"], CONFIG)
    if os.path.exists(path):
        with open(path, encoding="utf-8") as config:
            exec(config.read(), variables)  # pylint: disable=exec-used

    return [
        connection
        for connection in variables["user_connections"]
        if connection["type"] == "ldap" and not connection.get("disabled")
    ]


def connect(config):
    """
    Connect and bind with the credentials stored in the connection
    """
    connect_to = config["directory_type"][1]["connect_to"][1]
    server = connect_to.get("server") or connect_to["domain"]
    scheme = "ldaps" if "use_ssl" in config else "ldap"

    conn = ldap.initialize(f"{scheme}://{server}")
    conn.set_option(ldap.OPT_REFERRALS, 0)
    bind_dn, password = config.get("bind", ("", ("password", "")))
    conn.simple_bind_s(bind_dn, password_store.extract(password))
    return conn


def group_mail(group):
    """
    The mail address of an LDAP group, from the first connection knowing it
    """
    name = ldap.filter.escape_filter_chars(group)
    for config in ldap_connections():
        conn = connect(config)
        entries = conn.search_s(
            config["group_dn"],
            ldap.SCOPE_SUBTREE,
            f"(|(cn={name})(sAMAccountName={name}))",
            ["mail"],
        )
        for _dn, entry in entries:
            for address in entry.get("mail", []):
                return address.decode("utf-8")
    return None


def resolve(recipients):
    """
    Replace the group names of a recipient list by their mail address

    Entries containing an "@" are mailboxes and stay untouched, so groups and
    plain addresses can be mixed.
    """
    addresses = []
    for entry in recipients.replace(";", ",").split(","):
        entry = entry.strip()
        if not entry:
            continue
        if "@" in entry:
            addresses.append(entry)
        elif address := group_mail(entry):
            addresses.append(address)
        else:
            sys.stderr.write(f"No mail address found for LDAP group {entry!r}\n")

    if not addresses:
        sys.stderr.write("No recipient left after resolving LDAP groups\n")
        # Permanent problem, a retry would hit the same LDAP answer
        sys.exit(2)
    return ", ".join(addresses)


def rewrite(line):
    """
    Rewrite one line of a bulk notification context
    """
    if line.startswith("CONTACTEMAIL="):
        return "CONTACTEMAIL=%s\n" % resolve(line.split("=", 1)[1].strip())
    for own_key, mail_key in SENDER.items():
        if line.startswith(f"{own_key}="):
            return line.replace(own_key, mail_key, 1)
    return line


def main():
    """
    Resolve the recipients, then run the built-in mail plug-in
    """
    try:
        if "--bulk" in sys.argv:
            sys.stdin = io.StringIO("".join(rewrite(line) for line in sys.stdin))
        else:
            os.environ["NOTIFY_CONTACTEMAIL"] = resolve(
                os.environ.get("NOTIFY_CONTACTEMAIL", "")
            )
            for own_key, mail_key in SENDER.items():
                if value := os.environ.get(f"NOTIFY_{own_key}"):
                    os.environ[f"NOTIFY_{mail_key}"] = value
    except ldap.LDAPError as error:
        sys.stderr.write(f"LDAP error: {error}\n")
        sys.exit(2)

    from cmk.notification_plugins import mail  # pylint: disable=import-outside-toplevel

    mail.main()


if __name__ == "__main__":
    main()
