import base64, json, os, random, re, smtplib, ssl, string, sys
from email import encoders
from email.header import Header
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

UTF_8 = "utf-8"


def _cfg():
    return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))


def _randomword(n):
    return "".join(random.choice(string.ascii_lowercase) for _ in range(n))


def _header(s):
    if not s:
        return None
    return Header(" ".join(str(s).splitlines()))


def connect(cfg):
    host = str(cfg.get("host") or "")
    port = int(cfg.get("port") or 25)
    fqdn = (str(cfg.get("fqdn") or "").strip()) or None
    tls = str(cfg.get("tls") or "None")
    ctx = None
    if cfg.get("insecure"):
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE
    if tls == "SSL/TLS":
        server = smtplib.SMTP_SSL(host, port, local_hostname=fqdn, context=ctx, timeout=60)
    else:
        server = smtplib.SMTP(host, port, local_hostname=fqdn, timeout=60)
    server.ehlo()
    if tls == "STARTTLS" or str(tls).lower() == "true":
        server.starttls(context=ctx)
        server.ehlo()
    user = str(cfg.get("username") or "")
    if user:
        server.login(user, str(cfg.get("password") or ""))
    return server


def handle_html(html_body):
    """Extract inline data-URI images from the HTML and return (clean_body, image attachments)."""
    attachments = []
    clean = ""
    last = 0
    for i, m in enumerate(re.finditer(r'<img.+?src="(data:(image/.+?);base64,([a-zA-Z0-9+/=\r\n]+?))"', html_body, re.I)):
        _, subtype = m.group(2).split("/", 1)
        name = "image" + str(i) + "." + subtype
        cid = name + "@" + _randomword(8) + "." + _randomword(8)
        attachments.append({"subtype": subtype, "data": base64.b64decode(m.group(3)), "cid": cid})
        clean += html_body[last:m.start(1)] + "cid:" + cid
        last = m.end() - 1
    clean += html_body[last:]
    return clean, attachments


def split_fields(s):
    out = []
    for part in str(s or "").split(";"):
        if "=" in part:
            k, v = part.split("=", 1)
            if k.strip():
                out.append((k.strip(), v))
    return out


def build_message(cfg, inputs):
    to = [x.strip() for x in str(inputs.get("to") or "").split(",") if x.strip()]
    cc = [x.strip() for x in str(inputs.get("cc") or "").split(",") if x.strip()]
    bcc = [x.strip() for x in str(inputs.get("bcc") or "").split(",") if x.strip()]
    if not to and not cc and not bcc:
        raise Exception("at least one recipient (to, cc or bcc) is required")

    subject = str(inputs.get("subject") or "")
    body = str(inputs.get("body") or "")
    html_body = str(inputs.get("html_body") or "")
    att_name = inputs.get("attachment_name")
    att_b64 = inputs.get("attachment_base64")
    has_att = bool(att_name and att_b64)

    inline = []
    if html_body:
        html_body, inline = handle_html(html_body)

    if not html_body:
        if has_att:
            msg = MIMEMultipart()
            msg.attach(MIMEText(body, "plain", UTF_8))
        else:
            msg = MIMEText(body, "plain", UTF_8)
    else:
        if has_att or inline:
            msg = MIMEMultipart()
            if body:
                alt = MIMEMultipart("alternative")
                alt.attach(MIMEText(body, "plain", UTF_8))
                alt.attach(MIMEText(html_body, "html", UTF_8))
                msg.attach(alt)
            else:
                msg.attach(MIMEText(html_body, "html", UTF_8))
        elif body:
            msg = MIMEMultipart("alternative")
            msg.attach(MIMEText(body, "plain", UTF_8))
            msg.attach(MIMEText(html_body, "html", UTF_8))
        else:
            msg = MIMEText(html_body, "html", UTF_8)

    for img in inline:
        part = MIMEImage(img["data"], img["subtype"])
        part.add_header("Content-Disposition", "inline", filename=img["cid"].split("@")[0])
        part.add_header("Content-ID", "<" + img["cid"] + ">")
        msg.attach(part)

    if has_att:
        part = MIMEBase("application", "octet-stream")
        part.set_payload(base64.b64decode(att_b64))
        encoders.encode_base64(part)
        part.add_header("Content-Disposition", "attachment", filename=str(att_name))
        msg.attach(part)

    sender = str(inputs.get("sender") or cfg.get("from_address") or "")
    msg["Subject"] = _header(subject)
    msg["From"] = _header(sender)
    if inputs.get("reply_to"):
        msg["Reply-To"] = _header(inputs["reply_to"])
    if to:
        msg["To"] = _header(",".join(to))
    if cc:
        msg["CC"] = _header(",".join(cc))
    for name, value in split_fields(inputs.get("additional_headers")):
        msg[name] = _header(value)
    return sender, to + cc + bcc, msg.as_string()


def main():
    cfg = _cfg()
    inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
    sender, recipients, raw = build_message(cfg, inputs)
    if not sender:
        raise Exception("no sender address (set from_address in the config or pass sender)")
    server = connect(cfg)
    try:
        server.sendmail(sender, recipients, raw)
    finally:
        try:
            server.quit()
        except Exception:
            pass
    print(json.dumps({"ok": True, "sent_to": recipients}))


try:
    main()
except smtplib.SMTPException as e:
    print(json.dumps({"error": "SMTP error: " + str(e)}))
    sys.exit(1)
except Exception as e:
    print(json.dumps({"error": str(e)}))
    sys.exit(1)
