import asyncore
import email
import email.policy
import re
from smtpd import SMTPServer

from django.core.management.base import BaseCommand
from django.db import connections
from hc.api.models import Check

RE_UUID = re.compile(
    "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$"
)


def _match(subject, keywords):
    for s in keywords.split(","):
        s = s.strip()
        if s and s in subject:
            return True

    return False


def _process_message(remote_addr, mailfrom, mailto, data):
    to_parts = mailto.split("@")
    code = to_parts[0]

    try:
        data = data.decode()
    except UnicodeError:
        data = "[binary data]"

    if not RE_UUID.match(code):
        return f"Not an UUID: {code}"

    try:
        check = Check.objects.get(code=code)
    except Check.DoesNotExist:
        return f"Check not found: {code}"

    action = "success"
    if check.subject or check.subject_fail:
        action = "ign"
        # Specify policy, the default policy does not decode encoded headers:
        parsed = email.message_from_string(data, policy=email.policy.SMTP)
        subject = parsed.get("subject", "")
        if check.subject and _match(subject, check.subject):
            action = "success"
        elif check.subject_fail and _match(subject, check.subject_fail):
            action = "fail"

    ua = "Email from %s" % mailfrom
    check.ping(remote_addr, "email", "", ua, data, action)

    return f"Processed ping for {code}"


class Listener(SMTPServer):
    def __init__(self, localaddr, stdout):
        self.stdout = stdout
        super(Listener, self).__init__(localaddr, None, decode_data=False)

    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        # get a new db connection in case the old one has timed out:
        connections.close_all()

        result = _process_message(peer[0], mailfrom, rcpttos[0], data)
        self.stdout.write(result)


class Command(BaseCommand):
    help = "Listen for ping emails"

    def add_arguments(self, parser):
        parser.add_argument(
            "--host", help="ip address to listen on, default 0.0.0.0", default="0.0.0.0"
        )
        parser.add_argument(
            "--port", help="port to listen on, default 25", type=int, default=25
        )

    def handle(self, host, port, *args, **options):
        _ = Listener((host, port), self.stdout)
        print("Starting SMTP listener on %s:%d ..." % (host, port))
        asyncore.loop()
