from datetime import date, time, timedelta
from typing import BinaryIO, Optional, Union
from xml.dom import Node

from django.http import HttpRequest
from django.utils.translation import ugettext as _

import defusedxml

from aleksis.apps.chronos.models import Lesson, Room, Subject, TimePeriod
from aleksis.core.models import Group, Person
from aleksis.core.util import messages


def get_child_node_text(node: Node, tag: str) -> Optional[str]:
    tag_nodes = node.getElementsByTagName(tag)

    if len(tag_nodes) == 1:
        return tag_nodes[0].firstChild.nodeValue
    else:
        return None


def get_child_node_id(node: Node, tag: str) -> Optional[str]:
    tag_nodes = node.getElementsByTagName(tag)

    if len(tag_nodes) == 1:
        return tag_nodes[0].attributes["id"].value
    else:
        return None


def untis_import_xml(request: HttpRequest, untis_xml: Union[BinaryIO, str]) -> None:
    dom = defusedxml.parse(untis_xml)

    subjects = dom.getElementsByTagName("subject")
    for subject_node in subjects:
        short_name = subject_node.attributes["id"].value[3:]
        name = get_child_node_text(subject_node, "longname")
        colour_fg = get_child_node_text(subject_node, "forecolor")
        colour_bg = get_child_node_text(subject_node, "backcolor")

        Subject.objects.update_or_create(
            short_name=short_name,
            defaults={"name": name, "colour_fg": colour_fg, "colour_bg": colour_bg},
        )

    periods = dom.getElementsByTagName("timeperiod")
    for period_node in periods:
        weekday = int(get_child_node_text(period_node, "day"))
        period = int(get_child_node_text(period_node, "period"))
        starttime = get_child_node_text(period_node, "starttime")
        endtime = get_child_node_text(period_node, "endtime")

        time_start = time(int(starttime[:2]), int(starttime[2:]))
        time_end = time(int(endtime[:2]), int(endtime[2:]))

        TimePeriod.objects.update_or_create(
            weekday=weekday,
            period=period,
            defaults={"time_start": time_start, "time_end": time_end},
        )

    rooms = dom.getElementsByTagName("room")
    for room_node in rooms:
        short_name = room_node.attributes["id"].value[3:]
        name = get_child_node_text(room_node, "longname")

        Room.objects.update_or_create(short_name=short_name, defaults={"name": name})

    classes = dom.getElementsByTagName("class")
    for class_node in classes:
        short_name = class_node.attributes["id"].value[3:]
        name = _("Class %s") % short_name
        class_teacher_short_name = get_child_node_id(class_node, "class_teacher")[3:]

        class_, created = Group.objects.update_or_create(
            short_name=short_name, defaults={"name": name}
        )

        try:
            # Teachers need to come from another source, e.g. SchILD-NRW
            class_.owners.set([Person.objects.get(short_name=class_teacher_short_name)])
            class_.save()
        except Person.DoesNotExist:
            messages.warning(
                request,
                _("Could not set class teacher of %(class)s to %(teacher)s.")
                % {"class": short_name, "teacher": class_teacher_short_name},
            )

    # Set all existing lessons that overlap to end today
    today = date.today()
    Lesson.objects.filter(date_end__gt=today).update(date_end=today)

    lessons = dom.getElementsByTagName("lesson")
    for lesson_node in lessons:
        subject_short_name = get_child_node_id(lesson_node, "lesson_subject")[3:]
        teacher_short_name = get_child_node_id(lesson_node, "lesson_teacher")[3:]
        group_short_names = [
            v.strip()
            for v in get_child_node_id(lesson_node, "lesson_classes").split("CL_")
            if v.strip()
        ]
        effectivebegindate = get_child_node_text(lesson_node, "effectivebegindate")
        effectiveenddate = get_child_node_text(lesson_node, "effectiveenddate")

        times = lesson_node.getElementsByTagName("time")
        time_periods = []
        for time_node in times:
            day = int(get_child_node_text(time_node, "assigned_day"))
            period = int(get_child_node_text(time_node, "assigned_period"))

            room_id = get_child_node_id(time_node, "assigned_room")
            room = room_id[3:] if room_id else None

            time_periods.append((day, period, room))

        subject = Subject.objects.get(short_name=subject_short_name)
        periods = [
            (
                TimePeriod.objects.get(weekday=v[0], period=v[1]),
                Room.objects.get(short_name=v[2]) if v[2] else None,
            )
            for v in time_periods
        ]
        date_start = (
            date(
                int(effectivebegindate[:4]),
                int(effectivebegindate[4:6]),
                int(effectivebegindate[6:]),
            )
            if effectivebegindate
            else None
        )
        date_end = (
            date(
                int(effectiveenddate[:4]),
                int(effectiveenddate[4:6]),
                int(effectiveenddate[6:]),
            )
            if effectiveenddate
            else None
        )

        # Coerce effective start date to not be before tomorrow
        if date_start and date_start <= today:
            date_start = today + timedelta(days=1)

        try:
            groups = [Group.objects.get(short_name=v) for v in group_short_names]
        except Group.DoesNotExist:
            messages.error(
                request, _("Invalid list of classes: %s") % ", ".join(group_short_names)
            )
            continue

        try:
            teachers = [Person.objects.get(short_name=teacher_short_name)]
        except Person.DoesNotExist:
            messages.error(
                request,
                _("Failed to import lesson: Teacher %s does not exist.")
                % teacher_short_name,
            )
            continue

        lesson = Lesson.objects.create(
            subject=subject, date_start=date_start, date_end=date_end
        )

        lesson.groups.set(groups)
        lesson.teachers.set(teachers)

        for period in periods:
            lesson.periods.add(period[0], through_defaults={"room": period[1]})

        lesson.save()