# (c) cavaliba.com - sirene - mail.py

import os
import random
import re
import time
from datetime import datetime

from celery import shared_task
from django.conf import settings
from django.core import mail
from django.template.loader import render_to_string
from django.utils import timezone

import app_home.cache as cache
from app_home.configuration import get_configuration
from app_home.log import WARNING, log2


# ------------------------------------------------------------
@shared_task(ignore_result=True)
def task_send_mail(subject, text_content, dests, html_content=None, aaa=None):

    cache.init()

    sender = get_configuration("sirene", "EMAIL_FROM")
    result = sirene_send_mail(
        subject, text_content, sender, dests, html_content=html_content, aaa=aaa
    )

    return result


# ----------------------------------------------------------------------------
def mail_check_valid_address(email):

    if not email:
        return False

    if type(email) is not str:
        return False

    if not len(email) > 3:
        return False

    # regex = re.compile(r'([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+')
    regex = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,10}\b"

    if re.fullmatch(regex, email):
        return True

    return False


# ----------------------------------------------------------------------
#  sirene_html_email
# ----------------------------------------------------------------------
def sirene_html_email(message=None, updates=None):

    template = get_configuration("sirene", "EMAIL_MESSAGE_TEMPLATE")
    # relative to Django templates search path
    template_path = os.path.join("mail", template, "index.html")

    context = {
        "content": message.get_attribute_first("content"),
        "title": message.displayname,
        "severity": message.get_attribute_first("severity"),
        "category": message.get_attribute_first("category"),
        "created_by": message.get_attribute_first("created_by"),
        "created_at": message.get_attribute_first("created_at"),
    }

    if updates:
        context["updates"] = list(reversed(updates))

    # notify target
    ui_dict = message.get_dict_for_ui_detail(
        skip_external=True, skip_injected=False, skip_enumerate=False
    )
    # pprint.pprint(ui_dict)

    try:
        context["swidget"] = ui_dict["severity__widget"]["value"]
    except Exception:
        context["swidget"] = "&#x1F535;"

    r = []
    for fieldname in ui_dict.keys():
        if fieldname.startswith("notify_"):
            for target in ui_dict[fieldname]["value"]:
                # print("$$$", fieldname, ui_dict[fieldname]['value'])
                if type(target) is dict:
                    v = target.get("display", target.get("key", "?"))
                elif type(target) is list:
                    v = target[0]
                elif type(target) is str:
                    v = target
                else:
                    v = "n/a"
                if not v:
                    v = "n/a"
                r.append(v)
    context["notify_target"] = ", ".join(r)

    try:
        html_body = render_to_string(template_path, context)
    except Exception as e:
        log2.fail("mail.template", reason="render_failed", message=str(e))
        html_body = message.get_attribute_first("content")

    return html_body


# ----------------------------------------------------------------------
# sirene_send_mail  : dispatch to mode
# ----------------------------------------------------------------------
def sirene_send_mail(subject, text_content, sender, dests, html_content=None, aaa=None):
    """Send to dests with the EMAIL_MODE delivery mode; log one mail.send event with the
    full sent / failed / invalid address lists. Returns True if no delivery failed."""

    mode = get_configuration("sirene", "EMAIL_MODE")

    # limit subject length
    subject_cut = subject[:80]
    if len(subject_cut) != len(subject):
        subject_cut += " (...)"

    valid = [d for d in dests if mail_check_valid_address(d)]
    invalid = [d for d in dests if not mail_check_valid_address(d)]
    for dest in invalid:
        log2.debug("invalid email address: %s", dest, level=WARNING)

    senders = {"stdout": mail_mode_stdout, "folder": mail_mode_folder, "smtp": mail_mode_smtp}
    if mode in senders:
        sent, failed = senders[mode](subject_cut, text_content, sender, valid, html_content,
                                     aaa=aaa)  # fmt: skip
    else:
        sent, failed = [], list(valid)

    # one event per send, all recipients listed (decision 2026-10-18)
    extra = {"mode": mode, "recipients": len(dests), "sent": sent, "failed": failed,
             "invalid": invalid}  # fmt: skip
    if mode not in senders:
        log2.fail("mail.send", reason="unknown_mode", message=subject_cut, extra=extra)
    elif failed:
        log2.fail("mail.send", reason="partial" if sent else "send_failed", count=len(sent),
                  level=WARNING if sent else None, message=subject_cut, extra=extra)  # fmt: skip
    else:
        log2.ok("mail.send", count=len(sent), level=WARNING if invalid else None,
                message=subject_cut, extra=extra)  # fmt: skip
    return mode in senders and not failed


# ----------------------------------------------------------------------
# to STDOUT
# ----------------------------------------------------------------------
def mail_mode_stdout(subject, text_content, sender, dests, html_content=None, aaa=None):
    """Print one line per (valid) address. Returns (sent, failed)."""

    for dest in dests:
        print(f"MAIL (STDOUT) - {dest} - {subject}")
        log2.debug("mail (stdout) %s - %s", dest, subject)
    return list(dests), []


# ----------------------------------------------------------------------
# to Folder
# ----------------------------------------------------------------------


def mail_mode_folder(subject, text_content, sender, dests, html_content=None, aaa=None):
    """Write all (valid) addresses' mails to one JSON-ish file in CAVALIBA_MAIL_FOLDER.
    Returns (sent, failed)."""

    now = timezone.now()
    now_msec = time.time()
    filename = str(now_msec)
    filename = str(datetime.today().strftime("%Y-%m-%d-%H%M%S"))
    filename += "-"
    alea = int(random.random() * 1000000000)
    filename += str(alea)
    filename += ".json"

    mail_folder = settings.CAVALIBA_MAIL_FOLDER
    path = os.path.join(str(mail_folder), filename)

    sent = []
    with open(path, "a") as f:
        f.write("[")
        first = True
        for dest in dests:
            if not first:
                f.write(",\n")
            data = f"'timestamp': '{now}',\n'msec': {now_msec},\n"
            data += f"'from': {sender},\n"
            data += f"'to': '{dest}',\n"
            data += f"'subject': '{subject}'\n'"
            data += f"html_content': '{html_content}'\n"
            data += f"text_content': '{text_content}'\n"
            data = "{\n" + data + "}"
            f.write(data)
            sent.append(dest)
            first = False
            log2.debug("mail (folder) %s - %s", dest, subject)

        f.write("]\n")
        f.close()

    return sent, []


# ----------------------------------------------------------------
# SMTP
# ----------------------------------------------------------------


def mail_mode_smtp(subject, text_content, sender, dests, html_content=None, aaa=None):
    """Send (valid) addresses by EMAIL_SMTP_BATCH-sized SMTP batches; an address is failed
    when its batch failed. Returns (sent, failed)."""

    batch_size = int(get_configuration("sirene", "EMAIL_SMTP_BATCH"))
    sent, failed = [], []
    batch, batch_dests = [], []

    def flush():
        """Send the pending batch and file its addresses as sent or failed."""
        (sent if smtp_batch(batch, aaa=aaa) else failed).extend(batch_dests)
        batch.clear()
        batch_dests.clear()

    for dest in dests:
        msg = mail.EmailMultiAlternatives(subject, text_content, sender, [], [dest])
        if html_content:
            msg.attach_alternative(html_content, "text/html")
        batch.append(msg)
        batch_dests.append(dest)
        if len(batch) >= batch_size:
            flush()

    if batch:
        flush()

    log2.debug("smtp done: %s sent, %s failed - %s", len(sent), len(failed), subject)
    return sent, failed


def smtp_batch(batch, aaa=None):
    """Send one batch over a fresh SMTP connection. Returns True if the messages were sent
    (a failure to close the connection afterwards is only a warning)."""

    try:
        connection = mail.get_connection()
    except Exception as e:
        log2.fail("mail.smtp", reason="connect_failed", message=str(e))
        return False

    try:
        connection.send_messages(batch)
    except Exception as e:
        log2.fail("mail.smtp", reason="send_failed", message=str(e), count=len(batch))
        return False

    try:
        connection.close()
    except Exception as e:
        log2.fail("mail.smtp", reason="close_failed", message=str(e), level=WARNING)

    log2.debug("smtp batch sent: %s", len(batch))
    return True
