import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from typing import List, Optional


# TimeSpan class according to XSD: <TimeSpan Start="..." End="..." Duration="..." StartWindow="..." EndWindow="..."/>
class TimeSpan:
    def __init__(
        self,
        start: str,
        end: str = None,
        duration: str = None,
        start_window: str = None,
        end_window: str = None,
    ):
        self.start = start
        self.end = end
        self.duration = duration
        self.start_window = start_window
        self.end_window = end_window

    def to_xml(self):
        attrib = {"Start": self.start}
        if self.end:
            attrib["End"] = self.end
        if self.duration:
            attrib["Duration"] = self.duration
        if self.start_window:
            attrib["StartWindow"] = self.start_window
        if self.end_window:
            attrib["EndWindow"] = self.end_window
        return ET.Element(_ns("TimeSpan"), attrib)


NAMESPACE = "http://www.opentravel.org/OTA/2003/05"
ET.register_namespace("", NAMESPACE)


def _ns(tag):
    return f"{{{NAMESPACE}}}{tag}"


class ResGuest:
    def __init__(
        self,
        given_name: str,
        surname: str,
        gender: Optional[str] = None,
        birth_date: Optional[str] = None,
        language: Optional[str] = None,
        name_prefix: Optional[str] = None,
        name_title: Optional[str] = None,
        email: Optional[str] = None,
        address: Optional[dict] = None,
        telephones: Optional[list] = None,
    ):
        self.given_name = given_name
        self.surname = surname
        self.gender = gender
        self.birth_date = birth_date
        self.language = language
        self.name_prefix = name_prefix
        self.name_title = name_title
        self.email = email
        self.address = address or {}
        self.telephones = telephones or []

    def to_xml(self):
        resguest_elem = ET.Element(_ns("ResGuest"))
        profiles_elem = ET.SubElement(resguest_elem, _ns("Profiles"))
        profileinfo_elem = ET.SubElement(profiles_elem, _ns("ProfileInfo"))
        profile_elem = ET.SubElement(profileinfo_elem, _ns("Profile"))
        customer_elem = ET.SubElement(profile_elem, _ns("Customer"))
        if self.gender:
            customer_elem.set("Gender", self.gender)
        if self.birth_date:
            customer_elem.set("BirthDate", self.birth_date)
        if self.language:
            customer_elem.set("Language", self.language)
        personname_elem = ET.SubElement(customer_elem, _ns("PersonName"))
        if self.name_prefix:
            ET.SubElement(personname_elem, _ns("NamePrefix")).text = self.name_prefix
        ET.SubElement(personname_elem, _ns("GivenName")).text = self.given_name
        ET.SubElement(personname_elem, _ns("Surname")).text = self.surname
        if self.name_title:
            ET.SubElement(personname_elem, _ns("NameTitle")).text = self.name_title
        for tel in self.telephones:
            tel_elem = ET.SubElement(customer_elem, _ns("Telephone"))
            for k, v in tel.items():
                tel_elem.set(k, v)
        if self.email:
            ET.SubElement(customer_elem, _ns("Email")).text = self.email
        if self.address:
            address_elem = ET.SubElement(customer_elem, _ns("Address"))
            for k, v in self.address.items():
                if k == "CountryName":
                    country_elem = ET.SubElement(address_elem, _ns("CountryName"))
                    if isinstance(v, dict):
                        for ck, cv in v.items():
                            country_elem.set(ck, cv)
                    else:
                        country_elem.text = v
                else:
                    ET.SubElement(address_elem, _ns(k)).text = v
        return resguest_elem

    def __str__(self):
        from lxml import etree

        elem = self.to_xml()
        xml_bytes = ET.tostring(elem, encoding="utf-8")
        parser = etree.XMLParser(remove_blank_text=True)
        lxml_elem = etree.fromstring(xml_bytes, parser)
        return etree.tostring(lxml_elem, pretty_print=True, encoding="unicode")


class RoomStay:
    def __init__(self, room_type: str, timespan: TimeSpan, guests: List[ResGuest]):
        self.room_type = room_type
        self.timespan = timespan
        self.guests = guests

    def to_xml(self):
        roomstay_elem = ET.Element(_ns("RoomStay"))
        ET.SubElement(roomstay_elem, _ns("RoomType")).set(
            "RoomTypeCode", self.room_type
        )
        roomstay_elem.append(self.timespan.to_xml())
        guests_elem = ET.SubElement(roomstay_elem, _ns("Guests"))
        for guest in self.guests:
            guests_elem.append(guest.to_xml())
        return roomstay_elem

    def __str__(self):
        from lxml import etree

        elem = self.to_xml()
        xml_bytes = ET.tostring(elem, encoding="utf-8")
        parser = etree.XMLParser(remove_blank_text=True)
        lxml_elem = etree.fromstring(xml_bytes, parser)
        return etree.tostring(lxml_elem, pretty_print=True, encoding="unicode")


class Reservation:
    def __init__(
        self,
        reservation_id: str,
        hotel_code: str,
        roomstays: List[RoomStay],
        create_time: Optional[str] = None,
    ):
        self.reservation_id = reservation_id
        self.hotel_code = hotel_code
        self.roomstays = roomstays
        self.create_time = create_time or datetime.now(timezone.utc).isoformat()

    def to_xml(self):
        res_elem = ET.Element(_ns("HotelReservation"))
        uniqueid_elem = ET.SubElement(res_elem, _ns("UniqueID"))
        uniqueid_elem.set("Type", "14")
        uniqueid_elem.set("ID", self.reservation_id)
        hotel_elem = ET.SubElement(res_elem, _ns("Hotel"))
        hotel_elem.set("HotelCode", self.hotel_code)
        roomstays_elem = ET.SubElement(res_elem, _ns("RoomStays"))
        for rs in self.roomstays:
            roomstays_elem.append(rs.to_xml())
        res_elem.set("CreateDateTime", self.create_time)
        return res_elem

    def to_xml_string(self):
        root = ET.Element(
            _ns("OTA_ResRetrieveRS"),
            {"Version": "2024-10", "TimeStamp": datetime.now(timezone.utc).isoformat()},
        )
        success_elem = ET.SubElement(root, _ns("Success"))
        reservations_list = ET.SubElement(root, _ns("ReservationsList"))
        reservations_list.append(self.to_xml())
        return ET.tostring(root, encoding="utf-8", xml_declaration=True).decode("utf-8")
