#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2015, Matt Makai <matthew.makai@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = r'''
---
module: sendgrid
short_description: Sends an email with the SendGrid API
description:
  - "Sends an email with a SendGrid account through their API, not through
    the SMTP service."
notes:
  - "This module is non-idempotent because it sends an email through the
    external API. It is idempotent only in the case that the module fails."
  - "Like the other notification modules, this one requires an external
    dependency to work. In this case, you'll need an active SendGrid
    account."
  - "In order to use api_key, cc, bcc, attachments, from_name, html_body, headers
    you must pip install sendgrid"
requirements:
  - sendgrid Python library 1.6.22 or lower (Sendgrid API V2 supported)
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  username:
    type: str
    description:
      - Username for logging into the SendGrid account.
      - It is only required if O(api_key) is not supplied.
  password:
    type: str
    description:
      - Password that corresponds to the username.
      - It is only required if O(api_key) is not supplied.
  from_address:
    type: str
    description:
      - The address in the "from" field for the email.
    required: true
  to_addresses:
    type: list
    elements: str
    description:
      - A list with one or more recipient email addresses.
    required: true
  subject:
    type: str
    description:
      - The desired subject for the email.
    required: true
  api_key:
    type: str
    description:
      - Sendgrid API key to use instead of username/password.
  cc:
    type: list
    elements: str
    description:
      - A list of email addresses to cc.
  bcc:
    type: list
    elements: str
    description:
      - A list of email addresses to bcc.
  attachments:
    type: list
    elements: path
    description:
      - A list of relative or explicit paths of files you want to attach (7MB limit as per SendGrid docs).
  from_name:
    type: str
    description:
      - The name you want to appear in the from field, i.e 'John Doe'.
  html_body:
    description:
      - Whether the body is html content that should be rendered.
    type: bool
    default: false
  headers:
    type: dict
    description:
      - A dict to pass on as headers.
  body:
    type: str
    description:
      - The e-mail body content.
    required: true
author: "Matt Makai (@makaimc)"
'''

EXAMPLES = r'''
- name: Send an email to a single recipient that the deployment was successful
  community.general.sendgrid:
    username: "{{ sendgrid_username }}"
    password: "{{ sendgrid_password }}"
    from_address: "ansible@mycompany.com"
    to_addresses:
      - "ops@mycompany.com"
    subject: "Deployment success."
    body: "The most recent Ansible deployment was successful."
  delegate_to: localhost

- name: Send an email to more than one recipient that the build failed
  community.general.sendgrid:
      username: "{{ sendgrid_username }}"
      password: "{{ sendgrid_password }}"
      from_address: "build@mycompany.com"
      to_addresses:
        - "ops@mycompany.com"
        - "devteam@mycompany.com"
      subject: "Build failure!."
      body: "Unable to pull source repository from Git server."
  delegate_to: localhost
'''

# =======================================
# sendgrid module support methods
#
import os
import traceback

from ansible_collections.community.general.plugins.module_utils.version import LooseVersion

SENDGRID_IMP_ERR = None
try:
    import sendgrid
    HAS_SENDGRID = True
except ImportError:
    SENDGRID_IMP_ERR = traceback.format_exc()
    HAS_SENDGRID = False

from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils.urls import fetch_url


def post_sendgrid_api(module, username, password, from_address, to_addresses,
                      subject, body, api_key=None, cc=None, bcc=None, attachments=None,
                      html_body=False, from_name=None, headers=None):

    if not HAS_SENDGRID:
        SENDGRID_URI = "https://api.sendgrid.com/api/mail.send.json"
        AGENT = "Ansible"
        data = {'api_user': username, 'api_key': password,
                'from': from_address, 'subject': subject, 'text': body}
        encoded_data = urlencode(data)
        to_addresses_api = ''
        for recipient in to_addresses:
            recipient = to_bytes(recipient, errors='surrogate_or_strict')
            to_addresses_api += '&to[]=%s' % recipient
        encoded_data += to_addresses_api

        headers = {'User-Agent': AGENT,
                   'Content-type': 'application/x-www-form-urlencoded',
                   'Accept': 'application/json'}
        return fetch_url(module, SENDGRID_URI, data=encoded_data, headers=headers, method='POST')
    else:
        # Remove this check when adding Sendgrid API v3 support
        if LooseVersion(sendgrid.version.__version__) > LooseVersion("1.6.22"):
            module.fail_json(msg="Please install sendgrid==1.6.22 or lower since module uses Sendgrid V2 APIs.")

        if api_key:
            sg = sendgrid.SendGridClient(api_key)
        else:
            sg = sendgrid.SendGridClient(username, password)

        message = sendgrid.Mail()
        message.set_subject(subject)

        for recip in to_addresses:
            message.add_to(recip)

        if cc:
            for recip in cc:
                message.add_cc(recip)
        if bcc:
            for recip in bcc:
                message.add_bcc(recip)

        if headers:
            message.set_headers(headers)

        if attachments:
            for f in attachments:
                name = os.path.basename(f)
                message.add_attachment(name, f)

        if from_name:
            message.set_from('%s <%s.' % (from_name, from_address))
        else:
            message.set_from(from_address)

        if html_body:
            message.set_html(body)
        else:
            message.set_text(body)

        return sg.send(message)
# =======================================
# Main
#


def main():
    module = AnsibleModule(
        argument_spec=dict(
            username=dict(required=False),
            password=dict(required=False, no_log=True),
            api_key=dict(required=False, no_log=True),
            bcc=dict(required=False, type='list', elements='str'),
            cc=dict(required=False, type='list', elements='str'),
            headers=dict(required=False, type='dict'),
            from_address=dict(required=True),
            from_name=dict(required=False),
            to_addresses=dict(required=True, type='list', elements='str'),
            subject=dict(required=True),
            body=dict(required=True),
            html_body=dict(required=False, default=False, type='bool'),
            attachments=dict(required=False, type='list', elements='path')
        ),
        supports_check_mode=True,
        mutually_exclusive=[
            ['api_key', 'password'],
            ['api_key', 'username']
        ],
        required_together=[['username', 'password']],
    )

    username = module.params['username']
    password = module.params['password']
    api_key = module.params['api_key']
    bcc = module.params['bcc']
    cc = module.params['cc']
    headers = module.params['headers']
    from_name = module.params['from_name']
    from_address = module.params['from_address']
    to_addresses = module.params['to_addresses']
    subject = module.params['subject']
    body = module.params['body']
    html_body = module.params['html_body']
    attachments = module.params['attachments']

    sendgrid_lib_args = [api_key, bcc, cc, headers, from_name, html_body, attachments]

    if any(lib_arg is not None for lib_arg in sendgrid_lib_args) and not HAS_SENDGRID:
        reason = 'when using any of the following arguments: ' \
                 'api_key, bcc, cc, headers, from_name, html_body, attachments'
        module.fail_json(msg=missing_required_lib('sendgrid', reason=reason),
                         exception=SENDGRID_IMP_ERR)

    response, info = post_sendgrid_api(module, username, password,
                                       from_address, to_addresses, subject, body, attachments=attachments,
                                       bcc=bcc, cc=cc, headers=headers, html_body=html_body, api_key=api_key)

    if not HAS_SENDGRID:
        if info['status'] != 200:
            module.fail_json(msg="unable to send email through SendGrid API: %s" % info['msg'])
    else:
        if response != 200:
            module.fail_json(msg="unable to send email through SendGrid API: %s" % info['message'])

    module.exit_json(msg=subject, changed=False)


if __name__ == '__main__':
    main()
