import xml.etree.ElementTree as ET
from xml.dom import minidom
from collections import defaultdict
from ...models.models import DevisData


def add_if_value(parent, tag, value, attrib=None):
    if value not in (None, "", []):
        el = ET.SubElement(parent, tag, attrib or {})
        el.text = str(value)
        return el


def generate_xml(data):
    validated = DevisData.model_validate(data)

    NS = {
        "rsm": "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100",
        "ram": "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100",
        "udt": "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
    }

    root = ET.Element("rsm:CrossIndustryInvoice", {
        "xmlns:rsm": NS["rsm"],
        "xmlns:ram": NS["ram"],
        "xmlns:udt": NS["udt"]
    })

    # ================= CONTEXT =================
    ctx = ET.SubElement(root, "rsm:ExchangedDocumentContext")

    guideline = ET.SubElement(ctx, "ram:GuidelineSpecifiedDocumentContextParameter")
    ET.SubElement(guideline, "ram:ID").text = "urn:cen.eu:en16931:2017"

    # ================= DOCUMENT =================
    doc = ET.SubElement(root, "rsm:ExchangedDocument")
    ET.SubElement(doc, "ram:ID").text = data["invoice"]["order"]
    ET.SubElement(doc, "ram:TypeCode").text = data["invoice"]["type_code"]

    issue = ET.SubElement(doc, "ram:IssueDateTime")
    ET.SubElement(issue, "udt:DateTimeString", {
        "format": data["invoice"]["date_format"]
    }).text = data["invoice"]["date"]

    # Notes uniquement si présentes
    if data["invoice"].get("description"):
        for desc in data["invoice"]["description"]:
            if desc:
                note = ET.SubElement(doc, "ram:IncludedNote")
                ET.SubElement(note, "ram:Content").text = desc
                ET.SubElement(note, "ram:SubjectCode").text = "AAI"

    # ================= TRANSACTION =================
    sctt = ET.SubElement(root, "rsm:SupplyChainTradeTransaction")

    # ================= LINES =================
    vat_groups = defaultdict(float)

    for i, line in enumerate(data["invoice_lines"], start=1):
        item = ET.SubElement(sctt, "ram:IncludedSupplyChainTradeLineItem")

        doc_line = ET.SubElement(item, "ram:AssociatedDocumentLineDocument")
        ET.SubElement(doc_line, "ram:LineID").text = str(i)

        product = ET.SubElement(item, "ram:SpecifiedTradeProduct")
        add_if_value(product, "ram:SellerAssignedID", line.get("seller_id"))
        add_if_value(product, "ram:BuyerAssignedID", line.get("buyer_id"))
        ET.SubElement(product, "ram:Name").text = line["product_description"]

        agreement = ET.SubElement(item, "ram:SpecifiedLineTradeAgreement")
        price = ET.SubElement(agreement, "ram:NetPriceProductTradePrice")
        ET.SubElement(price, "ram:ChargeAmount").text = f"{line['unit_price']:.2f}"

        delivery = ET.SubElement(item, "ram:SpecifiedLineTradeDelivery")
        ET.SubElement(
            delivery,
            "ram:BilledQuantity",
            {"unitCode": line.get("unit", "C62")}
        ).text = str(line["quantity"])

        settlement = ET.SubElement(item, "ram:SpecifiedLineTradeSettlement")

        # TVA ligne
        tax = ET.SubElement(settlement, "ram:ApplicableTradeTax")
        ET.SubElement(tax, "ram:TypeCode").text = "VAT"
        ET.SubElement(tax, "ram:CategoryCode").text = line.get("tva_type", "S")
        ET.SubElement(tax, "ram:RateApplicablePercent").text = str(line["tva_code"])

        total_line = round(line["quantity"] * line["unit_price"], 2)
        vat_groups[line["tva_code"]] += total_line

        summation = ET.SubElement(
            settlement,
            "ram:SpecifiedTradeSettlementLineMonetarySummation"
        )
        ET.SubElement(
            summation,
            "ram:LineTotalAmount"
        ).text = f"{total_line:.2f}"

    # ================= HEADER AGREEMENT =================
    agreement = ET.SubElement(sctt, "ram:ApplicableHeaderTradeAgreement")

    add_if_value(agreement, "ram:BuyerReference", data.get("buyer_reference"))

    def build_party(parent, tag, party):
        p = ET.SubElement(parent, f"ram:{tag}")

        ET.SubElement(p, "ram:Name").text = party["name"]

        addr = ET.SubElement(p, "ram:PostalTradeAddress")

        ET.SubElement(addr, "ram:LineOne").text = party["address"]["adr_l1"]

        if party["address"].get("adr_l2"):
            ET.SubElement(addr, "ram:LineTwo").text = party["address"]["adr_l2"]

        if party["address"].get("adr_l3"):
            ET.SubElement(addr, "ram:LineThree").text = party["address"]["adr_l3"]

        ET.SubElement(addr, "ram:CityName").text = party["address"]["city"]
        ET.SubElement(addr, "ram:CountryID").text = party["address"]["country_id"]

        # TVA obligatoire si au moins une ligne en S
        if any(l.get("tva_type", "S") == "S" for l in data["invoice_lines"]):
            tax = ET.SubElement(p, "ram:SpecifiedTaxRegistration")
            ET.SubElement(tax, "ram:ID", {"schemeID": "VA"}).text = party.get("vat")

    build_party(agreement, "SellerTradeParty", data["company"])
    build_party(agreement, "BuyerTradeParty", data["partner"])

    # ================= DELIVERY =================
    ET.SubElement(sctt, "ram:ApplicableHeaderTradeDelivery")

    # ================= SETTLEMENT =================
    settlement = ET.SubElement(sctt, "ram:ApplicableHeaderTradeSettlement")
    ET.SubElement(settlement, "ram:InvoiceCurrencyCode").text = data["invoice"]["currency"]

    net_total = sum(vat_groups.values())
    vat_total = 0

    # TVA header groupée par taux
    for rate, base in vat_groups.items():
        vat_amount = round(base * rate / 100, 2)
        vat_total += vat_amount

        tax = ET.SubElement(settlement, "ram:ApplicableTradeTax")
        ET.SubElement(tax, "ram:CalculatedAmount").text = f"{vat_amount:.2f}"
        ET.SubElement(tax, "ram:TypeCode").text = "VAT"
        ET.SubElement(tax, "ram:BasisAmount").text = f"{base:.2f}"
        ET.SubElement(tax, "ram:CategoryCode").text = "S"
        ET.SubElement(tax, "ram:RateApplicablePercent").text = str(rate)

    # Payment terms obligatoire si montant > 0
    if net_total + vat_total > 0:
        payment = ET.SubElement(settlement, "ram:SpecifiedTradePaymentTerms")
        ET.SubElement(payment, "ram:Description").text = "Paiement à échéance"
        due = ET.SubElement(payment, "ram:DueDateDateTime")
        ET.SubElement(due, "udt:DateTimeString", {"format": "102"}).text = data["invoice"]["expiration"]

    totals = ET.SubElement(
        settlement,
        "ram:SpecifiedTradeSettlementHeaderMonetarySummation"
    )

    ET.SubElement(totals, "ram:LineTotalAmount").text = f"{net_total:.2f}"
    ET.SubElement(totals, "ram:TaxBasisTotalAmount").text = f"{net_total:.2f}"
    ET.SubElement(totals, "ram:TaxTotalAmount", {"currencyID": data["invoice"]["currency"]}).text = f"{vat_total:.2f}"
    ET.SubElement(totals, "ram:GrandTotalAmount").text = f"{net_total + vat_total:.2f}"
    ET.SubElement(totals, "ram:DuePayableAmount").text = f"{net_total + vat_total:.2f}"

    xml = ET.tostring(root, encoding="utf-8")
    return minidom.parseString(xml).toprettyxml(indent="  ", encoding="utf-8").decode()

import os

def write_xml_from_string(xml_string: str, destination_folder: str, file_name: str):
    """
    Writes an XML file from an already well-formed XML string.

    :param xml_string: String containing the full XML content
    :param destination_folder: Destination directory
    :param file_name: File name (with or without .xml extension)
    """

    # Add .xml extension if missing
    if not file_name.endswith(".xml"):
        file_name += ".xml"

    # Create destination folder if it does not exist
    os.makedirs(destination_folder, exist_ok=True)

    # Full file path
    file_path = os.path.join(destination_folder, file_name)

    # Write XML to file
    with open(file_path, "w", encoding="utf-8") as file:
        file.write(xml_string)

    return file_path
