"""
Return salt data via email

The following fields can be set in the minion conf file. Fields are optional
unless noted otherwise.

* ``from`` (required) The name/address of the email sender.
* ``to`` (required) The names/addresses of the email recipients;
    comma-delimited. For example: ``you@example.com,someoneelse@example.com``.
* ``host`` (required) The SMTP server hostname or address.
* ``port`` The SMTP server port; defaults to ``25``.
* ``username`` The username used to authenticate to the server. If specified a
    password is also required. It is recommended but not required to also use
    TLS with this option.
* ``password`` The password used to authenticate to the server.
* ``tls`` Whether to secure the connection using TLS; defaults to ``False``
* ``subject`` The email subject line.
* ``fields`` Which fields from the returned data to include in the subject line
    of the email; comma-delimited. For example: ``id,fun``. Please note, *the
    subject line is not encrypted*.
* ``gpgowner`` A user's :file:`~/.gpg` directory. This must contain a gpg
    public key matching the address the mail is sent to. If left unset, no
    encryption will be used. Requires :program:`python-gnupg` to be installed.
* ``template`` The path to a file to be used as a template for the email body.
* ``renderer`` A Salt renderer, or render-pipe, to use to render the email
    template. Default ``jinja``.

Below is an example of the above settings in a Salt Minion configuration file:

.. code-block:: yaml

    smtp.from: me@example.net
    smtp.to: you@example.com
    smtp.host: localhost
    smtp.port: 1025

Alternative configuration values can be used by prefacing the configuration.
Any values not found in the alternative configuration will be pulled from
the default location. For example:

.. code-block:: yaml

    alternative.smtp.username: saltdev
    alternative.smtp.password: saltdev
    alternative.smtp.tls: True

To use the SMTP returner, append '--return smtp' to the ``salt`` command.

.. code-block:: bash

    salt '*' test.ping --return smtp

To use the alternative configuration, append '--return_config alternative' to the ``salt`` command.

.. versionadded:: 2015.5.0

.. code-block:: bash

    salt '*' test.ping --return smtp --return_config alternative

To override individual configuration items, append --return_kwargs '{"key:": "value"}' to the
``salt`` command.

.. versionadded:: 2016.3.0

.. code-block:: bash

    salt '*' test.ping --return smtp --return_kwargs '{"to": "user@domain.com"}'

An easy way to test the SMTP returner is to use the development SMTP server
built into Python. The command below will start a single-threaded SMTP server
that prints any email it receives to the console.

.. code-block:: python

    python -m smtpd -n -c DebuggingServer localhost:1025

.. versionadded:: 2016.11.0

It is possible to send emails with selected Salt events by configuring ``event_return`` option
for Salt Master. For example:

.. code-block:: yaml

    event_return: smtp

    event_return_whitelist:
      - salt/key

    smtp.from: me@example.net
    smtp.to: you@example.com
    smtp.host: localhost
    smtp.subject: 'Salt Master {{act}}ed key from Minion ID: {{id}}'
    smtp.template: /srv/salt/templates/email.j2

Also you need to create additional file ``/srv/salt/templates/email.j2`` with email body template:

.. code-block:: yaml

    act: {{act}}
    id: {{id}}
    result: {{result}}

This configuration enables Salt Master to send an email when accepting or rejecting minions keys.
"""

import io
import logging
import os
import smtplib
from email.utils import formatdate

import salt.loader
import salt.returners
import salt.utils.jid
from salt.template import compile_template

try:
    import gnupg

    HAS_GNUPG = True
except ImportError:
    HAS_GNUPG = False


log = logging.getLogger(__name__)

__virtualname__ = "smtp"


def __virtual__():
    return __virtualname__


def _get_options(ret=None):
    """
    Get the SMTP options from salt.
    """
    attrs = {
        "from": "from",
        "to": "to",
        "host": "host",
        "port": "port",
        "username": "username",
        "password": "password",
        "subject": "subject",
        "gpgowner": "gpgowner",
        "fields": "fields",
        "tls": "tls",
        "renderer": "renderer",
        "template": "template",
    }

    _options = salt.returners.get_returner_options(
        __virtualname__, ret, attrs, __salt__=__salt__, __opts__=__opts__
    )
    return _options


def returner(ret):
    """
    Send an email with the data
    """

    _options = _get_options(ret)
    from_addr = _options.get("from")
    to_addrs = _options.get("to").split(",")
    host = _options.get("host")
    port = _options.get("port")
    user = _options.get("username")
    passwd = _options.get("password")
    subject = _options.get("subject") or "Email from Salt"
    gpgowner = _options.get("gpgowner")
    fields = _options.get("fields").split(",") if "fields" in _options else []
    smtp_tls = _options.get("tls")

    renderer = _options.get("renderer") or "jinja"
    rend = salt.loader.render(__opts__, {})
    blacklist = __opts__.get("renderer_blacklist")
    whitelist = __opts__.get("renderer_whitelist")

    if not port:
        port = 25
    log.debug("SMTP port has been set to %s", port)

    for field in fields:
        if field in ret:
            subject += f" {ret[field]}"
    subject = compile_template(
        ":string:", rend, renderer, blacklist, whitelist, input_data=subject, **ret
    )
    if isinstance(subject, io.StringIO):
        subject = subject.read()
    log.debug("smtp_return: Subject is '%s'", subject)

    template = _options.get("template")
    if template:
        content = compile_template(
            template, rend, renderer, blacklist, whitelist, **ret
        )
    else:
        template = (
            "id: {{id}}\r\n"
            "function: {{fun}}\r\n"
            "function args: {{fun_args}}\r\n"
            "jid: {{jid}}\r\n"
            "return: {{return}}\r\n"
        )
        content = compile_template(
            ":string:", rend, renderer, blacklist, whitelist, input_data=template, **ret
        )

    if gpgowner:
        if HAS_GNUPG:
            gpg = gnupg.GPG(
                gnupghome=os.path.expanduser(f"~{gpgowner}/.gnupg"),
                options=["--trust-model always"],
            )
            encrypted_data = gpg.encrypt(content, to_addrs)
            if encrypted_data.ok:
                log.debug("smtp_return: Encryption successful")
                content = str(encrypted_data)
            else:
                log.error(
                    "smtp_return: Encryption failed, only an error message will be sent"
                )
                content = "Encryption failed, the return data was not sent.\r\n\r\n{}\r\n{}".format(
                    encrypted_data.status, encrypted_data.stderr
                )
        else:
            log.error(
                "gnupg python module is required in order to user gpgowner in smtp"
                " returner ; ignoring gpgowner configuration for now"
            )
    if isinstance(content, io.StringIO):
        content = content.read()

    message = "From: {}\r\nTo: {}\r\nDate: {}\r\nSubject: {}\r\n\r\n{}".format(
        from_addr, ", ".join(to_addrs), formatdate(localtime=True), subject, content
    )

    log.debug("smtp_return: Connecting to the server...")
    server = smtplib.SMTP(host, int(port))
    if smtp_tls is True:
        server.starttls()
        log.debug("smtp_return: TLS enabled")
    if user and passwd:
        server.login(user, passwd)
        log.debug("smtp_return: Authenticated")
    # enable logging SMTP session after the login credentials were passed
    server.set_debuglevel(1)
    server.sendmail(from_addr, to_addrs, message)
    log.debug("smtp_return: Message sent.")
    server.quit()


def prep_jid(nocache=False, passed_jid=None):  # pylint: disable=unused-argument
    """
    Do any work necessary to prepare a JID, including sending a custom id
    """
    return passed_jid if passed_jid is not None else salt.utils.jid.gen_jid(__opts__)


def event_return(events):
    """
    Return event data via SMTP
    """

    for event in events:
        ret = event.get("data", False)

        if ret:
            returner(ret)
