#!/usr/bin/env python3

import argparse
from collections import namedtuple
from email.message import EmailMessage
from email.headerregistry import Address
import os
import smtplib
import sys
from urllib.parse import quote

from bs4 import BeautifulSoup

from nspylib.clients import LDAP
from nscli.auth.ns_auth import Token
from nscli.config import settings
from nscli.shared.helpers import yes_no_prompt

LDAPAttrs = namedtuple("LDAPAttrs", field_names=["mail", "display_name", "title"])
Relay = namedtuple("Relay", field_names=["hostname", "port"])
RELAYS = {
    "ORD": Relay("smtp.ord1.corp.rackspace.com", 587),
    "DFW": Relay("smtp.dfw1.corp.rackspace.com", 587),
    "IAD": Relay("smtp.iad2.corp.rackspace.com", 587),
    "LON": Relay("smtp.lon3.corp.rackspace.com", 587),
    "HKG": Relay("smtp.hkg1.corp.rackspace.com", 587),
    "SYD": Relay("smtp.syd2.corp.rackspace.com", 587),
    "FRA": Relay("smtp.lon3.corp.rackspace.com", 587),
}


class Email(object):

    announce_list = (
        Address("GTSNetSec", addr_spec="GTSNetSec@rackspace.com"),
        Address("NSCLI Dev", addr_spec="nscli@rackspace.com"),
        Address("NSI Stakeholders", addr_spec="nsi_stakeholders@rackspace.com"),
        Address("GTS Network Defense", addr_spec="GTS-Network-Defense@rackspace.com"),
    )

    def __init__(self, for_real=False):
        if for_real:
            self.subject = "New NSCLI Version Available"
            self._sendto = self.announce_list
        else:
            self.subject = "NSCLI Release Email Test"
            self._sendto = None
        self._sender = None
        self._sender_email = None
        self._sender_display_name = None
        self._html_body = None
        self._ldap_attrs = None
        self._txt_body = None
        self._signature = None
        self._relay_info = None
        self._nscli_token = None

    def send(self):
        """Send the email"""
        message = self.message
        with smtplib.SMTP(self.relay_info.hostname, self.relay_info.port) as relay:
            try:
                relay.ehlo()
                relay.starttls()
                relay.ehlo()
                relay.send_message(message)
            except smtplib.SMTPException as e:
                print("Error from relay: {}".format(str(e)), file=sys.stderr)
                return e

    @property
    def nscli_token(self):
        if not self._nscli_token:
            self._nscli_token = Token()
        return self._nscli_token

    @property
    def sendto(self):
        """The recipient's address(es)"""
        if not self._sendto:
            self._sendto = self.sender
        return self._sendto

    @property
    def relay_info(self):
        """Info about the mail relay to be used"""
        if not self._relay_info:
            region = settings.get_region()
            self._relay_info = RELAYS.get(region.upper()) or RELAYS["ORD"]
        return self._relay_info

    @property
    def sender(self):
        """Sender's e-mail address"""
        if not self._sender:
            self._sender = Address(self.ldap_attrs.display_name, addr_spec=self.ldap_attrs.mail)
        return self._sender

    @property
    def ldap_attrs(self):
        if not self._ldap_attrs:
            ldap = LDAP(self.nscli_token.sso_user, self.nscli_token.sso_password)
            ldap.search(
                "dc=rackspace,dc=corp",
                "(&(objectCategory=person)(objectClass=user)(samAccountName={}))".format(self.nscli_token.sso_user),
                attributes=["mail", "displayName", "title"]
            )
            res = ldap.entries[0].entry_attributes_as_dict
            self._ldap_attrs = LDAPAttrs(res["mail"][0], res["displayName"][0], res["title"][0])
        return self._ldap_attrs

    @property
    def signature(self):
        """The bit at the end of the e-mail"""
        if not self._signature:
            self._signature = "Thanks,\n{}\n{}".format(self.ldap_attrs.display_name, self.ldap_attrs.title)
        return self._signature

    @property
    def message(self):
        """The whole EmailMessage, ready for sending."""
        msg = EmailMessage()
        msg["Subject"] = self.subject
        msg["From"] = self.sender
        msg["To"] = self.sendto
        msg.set_content(self.txt_body)
        msg.add_alternative(self.html_body, subtype="html")
        return msg

    @property
    def html_body(self):
        """The HTML version of the e-mail"""
        path = os.path.normpath(
            os.path.join(
                os.path.dirname(os.path.abspath(__file__)), os.path.pardir, "docs", "email", "ReleaseEmail.html"
            )
        )
        with open(path) as f:
            html = f.read()

        soup = BeautifulSoup(html, "html.parser")
        preamble = soup.find("section", attrs={"id": "body"})
        preamble.find("h2").extract()  # Remove "Body" from the body
        changelog = soup.find("section", attrs={"id": "changelog"})
        signature = BeautifulSoup("<br>" + self.signature.replace("\n", "<br>"), "html.parser")
        sections = (preamble, changelog, signature)

        for section in sections:

            # Remove permalinks to headers
            for permalink in section.find_all("a", attrs={"title": "Permalink to this headline"}):
                permalink.extract()

            # Remove gratuitous spans from the insides of e-mail links
            for link in section.find_all("a", attrs={"class": "reference external"}):
                if "mailto:" in link.attrs.get("href", ""):
                    address = link.attrs["href"].partition(":")[2]
                    while link.span:
                        link.span.unwrap()
                    link_text_no_spaces = ''.join(link.get_text().split())
                    if address.lower() == quote(link_text_no_spaces.lower()):
                        link.string = link_text_no_spaces

        return "\n".join([s.prettify() for s in sections])

    @property
    def txt_body(self):
        """The plain-text (RST) version of the e-mail"""
        folder = os.path.normpath(
            os.path.join(
                os.path.dirname(os.path.abspath(__file__)), os.path.pardir, "docs", "src"
            )
        )
        files = (
            os.path.join(folder, "ReleaseEmail.rst"),
            os.path.join(folder, "CurrentChangelog.rst")
        )
        parts = []
        for file_ in files:
            with open(file_) as f:
                parts.append(f.read())
        raw_lines = "\n".join(parts).splitlines()
        body_lines = [i for i in raw_lines if not i.startswith(".. ")]  # omit RST junk
        body_lines = body_lines[body_lines.index("Hello, Rackers!"):]  # omit sender, subject, etc.
        body_lines.extend(["", self.signature])
        return "\n".join(body_lines)


def build_cli_parser():
    parser = argparse.ArgumentParser(description="Send out the nscli release e-mail.")
    test_or_real = parser.add_mutually_exclusive_group()
    test_or_real.add_argument(
        "-t", "--test",
        help="Send a test e-mail to yourself only.",
        dest="real", action="store_false", default=None
    )
    test_or_real.add_argument(
        "-r", "--real",
        help="Send the real announcement e-mail to everyone.",
        dest="real", action="store_true", default=None
    )
    parser.add_argument(
        "-y", "--yes",
        help="auto-yes: don't prompt for confirmation.",
        action="store_true"
    )
    return parser


def main(args=sys.argv):
    parser = build_cli_parser()
    args = parser.parse_args(args[1:])
    real = args.real
    if real or real is None:
        if real and args.yes:
            real = True
        elif yes_no_prompt(
            "Do you really want to send out the release e-mail to hundreds of people? "
            "('N' to send a test e-mail instead)"
        ) and yes_no_prompt("Really send the release email?"):
            real = True
    email = Email(for_real=real)
    return email.send()


if __name__ == '__main__':
    sys.exit(main(sys.argv))
