import configparser
import enum
import getpass
import itertools
import smtplib
import textwrap

import xml.dom.minidom

from datetime import datetime


class Status(enum.Enum):
    PROTO = 'ProtoXEP'
    EXPERIMENTAL = 'Experimental'
    PROPOSED = 'Proposed'
    DRAFT = 'Draft'
    ACTIVE = 'Active'
    FINAL = 'Final'
    RETRACTED = 'Retracted'
    OBSOLETE = 'Obsolete'
    DEFERRED = 'Deferred'
    REJECTED = 'Rejected'
    DEPRECATED = 'Deprecated'

    @classmethod
    def fromstr(cls, s):
        if s == "Proto" or s.lower() == "protoxep":
            s = "ProtoXEP"
        return cls(s)


class Action(enum.Enum):
    PROTO = "Proposed XMPP Extension"
    NEW = "NEW"
    DRAFT = "DRAFT"
    ACTIVE = "ACTIVE"
    FINAL = "FINAL"
    RETRACT = "RETRACTED"
    OBSOLETE = "OBSOLETED"
    DEFER = "DEFERRED"
    UPDATE = "UPDATED"
    DEPRECATE = "DEPRECATED"
    LAST_CALL = "LAST CALL"
    REJECT = "REJECT"

    @classmethod
    def fromstatus(cls, status):
        return {
            Status.EXPERIMENTAL: cls.NEW,
            Status.DRAFT: cls.DRAFT,
            Status.ACTIVE: cls.ACTIVE,
            Status.FINAL: cls.FINAL,
            Status.RETRACTED: cls.RETRACT,
            Status.OBSOLETE: cls.OBSOLETE,
            Status.DEPRECATED: cls.DEPRECATE,
            Status.DEFERRED: cls.DEFER,
            Status.PROPOSED: cls.LAST_CALL,
            Status.REJECTED: cls.REJECT,
        }[status]

    @property
    def display_name(self):
        if self == self.DRAFT:
            return "STABLE"
        return self.value


def load_xepinfo(el):
    accepted = el.get("accepted").lower() == "true"

    info = {
        "title": el.find("title").text,
        "abstract": el.find("abstract").text,
        "type": el.find("type").text,
        "status": Status.fromstr(el.find("status").text),
        "approver": el.find("approver").text,
        "accepted": accepted,
    }

    last_revision_el = el.find("last-revision")
    if last_revision_el is not None:
        last_revision = {
            "version": last_revision_el.find("version").text,
            "date": datetime.strptime(
                last_revision_el.find("date").text,
                "%Y-%m-%d",
            ),
            "initials": None,
            "remark": None,
        }

        initials_el = last_revision_el.find("initials")
        if initials_el is not None:
            last_revision["initials"] = initials_el.text

        remark_el = last_revision_el.find("remark")
        if remark_el is not None:
            last_revision["remark"] = remark_el.text

        info["last_revision"] = last_revision

    last_call_el = el.find("lastcall")
    if last_call_el is not None:
        info["last_call"] = last_call_el.text

    sig = el.find("sig")
    if sig is not None:
        info["sig"] = sig.text

    if accepted:
        info["number"] = int(el.find("number").text)
    else:
        info["protoname"] = el.find("proto-name").text

    return info


def load_xepinfos(tree):
    accepted, protos = {}, {}
    for info_el in tree.getroot():
        info = load_xepinfo(info_el)
        if info["accepted"]:
            accepted[info["number"]] = info
        else:
            protos[info["protoname"]] = info

    return accepted, protos


def minidom_find_child(elem, child_tag):
    for child in elem.childNodes:
        if hasattr(child, "tagName") and child.tagName == child_tag:
            return child
    return None


def minidom_find_header(document):
    header = minidom_find_child(document.documentElement, "header")
    if header is None:
        raise ValueError("cannot find <header/>")
    return header


def minidom_get_text(elem):
    return "".join(
        child.nodeValue
        for child in elem.childNodes
        if isinstance(child, (xml.dom.minidom.Text,
                              xml.dom.minidom.CDATASection))
    )


def minidom_children(elem):
    return [
        child for child in elem.childNodes
        if isinstance(child, (xml.dom.minidom.Element))
    ]


def choose(prompt, options, *,
           eof=EOFError,
           keyboard_interrupt=KeyboardInterrupt):
    while True:
        try:
            choice = input(prompt).strip()
        except EOFError:
            if eof is EOFError:
                raise
            return eof
        except KeyboardInterrupt:
            if keyboard_interrupt is KeyboardInterrupt:
                raise
            return keyboard_interrupt

        if choice not in options:
            print("invalid choice. please enter one of: {}".format(
                ", ".join(map(str, options))
            ))
            continue

        return choice


def get_or_ask(config, section, name, prompt):
    try:
        return config.get(section, name)
    except (configparser.NoSectionError,
            configparser.NoOptionError):
        return input(prompt)


def interactively_extend_smtp_config(config):
    try:
        host = config.get("smtp", "host")
    except (configparser.NoSectionError,
            configparser.NoOptionError):
        host = input("SMTP server: ").strip()
        port = int(input("SMTP port (blank for 587): ").strip() or "587")
        user = input(
            "SMTP user (leave blank for anon): "
        ).strip() or None
        if user:
            password = getpass.getpass()
        else:
            password = None
    else:
        port = config.getint("smtp", "port", fallback=587)
        user = config.get("smtp", "user", fallback=None)
        password = config.get("smtp", "password", fallback=None)

    try:
        from_ = config.get("smtp", "from")
    except (configparser.NoSectionError,
            configparser.NoOptionError):
        from_ = input("From address: ").strip()

    if not config.has_section("smtp"):
        config.add_section("smtp")
    config.set("smtp", "host", host)
    config.set("smtp", "port", str(port))
    if user:
        config.set("smtp", "user", user)
        if password is None:
            password = getpass.getpass()
        config.set("smtp", "password", password)
    config.set("smtp", "from", from_)


def make_smtpconn(config):
    host = config.get("smtp", "host")
    port = config.getint("smtp", "port")
    user = config.get("smtp", "user", fallback=None)
    password = config.get("smtp", "password", fallback=None)

    conn = smtplib.SMTP(host, port)
    conn.starttls()
    if user is not None:
        conn.login(user, password)

    return conn


def make_fake_smtpconn():
    class Fake:
        def send_message(self, mail):
            print("---8<---")
            print(mail.as_string())
            print("--->8---")

        def close(self):
            pass

    return Fake()


def wraptext(text):
    return "\n".join(
        itertools.chain(
            *[textwrap.wrap(line) if line else [line] for line in text.split("\n")]
        )
    )