"""One STARTTLS SMTP submission. SMTP acceptance does not prove inbox delivery."""

import json
import os
import re
import smtplib
import ssl
from email.message import EmailMessage
from email.utils import formatdate, make_msgid, parseaddr


def required(env, key):
    value = env.get(key, "")
    if not value or any(char in value for char in "\r\n\x00"):
        raise ValueError(key)
    return value


def address(env, key):
    value = required(env, key)
    display_name, parsed = parseaddr(value)
    # Deliberately accept one plain ASCII address, not a recipient list.
    if (display_name or parsed != value or not value.isascii()
            or value.count("@") != 1 or any(c in value for c in " ,;<>")):
        raise ValueError(key)
    local, domain = value.rsplit("@", 1)
    if not local or not domain:
        raise ValueError(key)
    return value


def build(env):
    config = {key: required(env, key) for key in
              ("SMTP_HOST", "SMTP_USERNAME", "SMTP_PASSWORD")}
    config["SMTP_PORT"] = int(env.get("SMTP_PORT", "587"))
    if not 1 <= config["SMTP_PORT"] <= 65535:
        raise ValueError("SMTP_PORT")
    sender, recipient = address(env, "MAIL_FROM"), address(env, "MAIL_TO")
    repo, ref, sha = (required(env, key) for key in
                      ("GITHUB_REPOSITORY", "GITHUB_REF", "GITHUB_SHA"))
    run_id, attempt = (required(env, key) for key in
                       ("GITHUB_RUN_ID", "GITHUB_RUN_ATTEMPT"))
    server = required(env, "GITHUB_SERVER_URL").rstrip("/")
    actor = required(env, "GITHUB_ACTOR")
    if not re.fullmatch(r"[0-9a-fA-F]{40}", sha):
        raise ValueError("GITHUB_SHA")
    if not run_id.isdigit() or not attempt.isdigit() or not server.startswith("https://"):
        raise ValueError("run metadata")
    run_url = f"{server}/{repo}/actions/runs/{run_id}/attempts/{attempt}"
    message = EmailMessage()
    message["From"], message["To"] = sender, recipient
    message["Subject"] = f"[GitHub push] {repo} {sha[:12]}"
    message["Date"] = formatdate(localtime=False)
    message["Message-ID"] = make_msgid(domain=sender.rsplit("@", 1)[1])
    message.set_content(
        f"Repository: {repo}\nRef: {ref}\nCommit: {sha}\n"
        f"Actor: {actor}\nRun: {run_url}\n"
        "This notice records a push, not deployment success.\n"
    )
    record = {"sha": sha, "run_id": run_id, "attempt": attempt,
              "message_id": message["Message-ID"]}
    return config, sender, recipient, message, record


def emit(stage, **fields):
    print(json.dumps({"stage": stage, **fields}, ensure_ascii=True))


def main(environ=None, smtp_factory=None):
    env = os.environ if environ is None else environ
    try:
        config, sender, recipient, message, record = build(env)
    except (ValueError, TypeError):
        emit("configuration_error")
        return 2
    factory = smtplib.SMTP if smtp_factory is None else smtp_factory
    try:
        with factory(config["SMTP_HOST"], config["SMTP_PORT"], timeout=30) as smtp:
            smtp.ehlo()
            smtp.starttls(context=ssl.create_default_context())
            smtp.ehlo()
            smtp.login(config["SMTP_USERNAME"], config["SMTP_PASSWORD"])
            refused = smtp.send_message(message, from_addr=sender, to_addrs=[recipient])
            if refused:
                emit("smtp_recipient_refused")
                return 1
    except smtplib.SMTPAuthenticationError:
        emit("smtp_authentication_failed")
        return 1
    except smtplib.SMTPRecipientsRefused:
        emit("smtp_recipient_refused")
        return 1
    except (smtplib.SMTPException, OSError):
        # Provider exceptions can contain addresses or credentials; omit their text.
        emit("smtp_error")
        return 1
    emit("smtp_accepted", **record)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
