"""
Fusionne un PDF visuel de facture et un XML CII pour produire un fichier
Factur-X complet et valide : PDF/A-3 avec le XML embarqué en pièce jointe
et les métadonnées XMP requises par la norme.

Usage :
    python merge_facturx.py facture_visuelle.pdf facture_cii.xml facture_facturx.pdf
"""

import sys
from datetime import datetime, timezone

import pikepdf
from pikepdf import Name, Array, Dictionary, String
from PIL import ImageCms

# Profil ICC sRGB requis pour la conformité PDF/A-3. Plutôt que de dépendre
# d'un fichier .icc installé sur le système (chemin qui varie selon la
# distribution : Ubuntu/Debian, CentOS/RHEL, etc., et qui peut être absent),
# on le génère directement en mémoire via littlecms (embarqué dans Pillow).
# Ceci rend le script portable sur n'importe quel système où Pillow est
# installé, sans dépendance à un paquet de profils de couleur externe.

NS_FX = "urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#"
NS_PDFAID = "http://www.aiim.org/pdfa/ns/id/"


# Valeurs de ConformanceLevel acceptées par les validateurs (avec espace pour
# le profil complet, contrairement à l'URN de profil qui s'écrit sans espace).
NIVEAUX_CONFORMITE_XMP = {
    "MINIMUM": "MINIMUM",
    "BASIC": "BASIC",
    "BASICWL": "BASIC WL",
    "EN16931": "EN 16931",
    "EXTENDED": "EXTENDED",
}


def construire_xmp(
    numero_facture: str,
    profil_urn: str,
    niveau_conformite: str,
    date_document: str,
) -> str:
    """Construit le paquet XMP incluant les métadonnées Factur-X (namespace fx),
    la déclaration de conformité PDF/A-3 (namespace pdfaid), et le schéma
    d'extension PDF/A décrivant le namespace fx: (obligatoire : sans cette
    déclaration, un validateur PDF/A strict rejette les propriétés custom
    comme non conformes à la clause 6.6.2.3)."""

    date_xmp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
    niveau_xmp = NIVEAUX_CONFORMITE_XMP.get(niveau_conformite, niveau_conformite)

    return f"""<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:dc="http://purl.org/dc/elements/1.1/"
        xmlns:pdf="http://ns.adobe.com/pdf/1.3/"
        xmlns:xmp="http://ns.adobe.com/xap/1.0/"
        xmlns:pdfaid="{NS_PDFAID}"
        xmlns:fx="{NS_FX}">
      <dc:title>
        <rdf:Alt><rdf:li xml:lang="x-default">Facture {numero_facture}</rdf:li></rdf:Alt>
      </dc:title>
      <dc:creator><rdf:Seq><rdf:li>Facturation</rdf:li></rdf:Seq></dc:creator>
      <dc:description>
        <rdf:Alt><rdf:li xml:lang="x-default">Facture Factur-X {numero_facture}</rdf:li></rdf:Alt>
      </dc:description>
      <pdf:Producer>pikepdf / script maison Factur-X</pdf:Producer>
      <xmp:CreateDate>{date_xmp}</xmp:CreateDate>
      <xmp:MetadataDate>{date_xmp}</xmp:MetadataDate>

      <pdfaid:part>3</pdfaid:part>
      <pdfaid:conformance>B</pdfaid:conformance>

      <fx:DocumentType>INVOICE</fx:DocumentType>
      <fx:DocumentFileName>factur-x.xml</fx:DocumentFileName>
      <fx:Version>1.0</fx:Version>
      <fx:ConformanceLevel>{niveau_xmp}</fx:ConformanceLevel>
    </rdf:Description>
    <rdf:Description rdf:about=""
        xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/"
        xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#"
        xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">
      <pdfaExtension:schemas>
        <rdf:Bag>
          <rdf:li rdf:parseType="Resource">
            <pdfaSchema:schema>Factur-X PDFA Extension Schema</pdfaSchema:schema>
            <pdfaSchema:namespaceURI>{NS_FX}</pdfaSchema:namespaceURI>
            <pdfaSchema:prefix>fx</pdfaSchema:prefix>
            <pdfaSchema:property>
              <rdf:Seq>
                <rdf:li rdf:parseType="Resource">
                  <pdfaProperty:name>DocumentFileName</pdfaProperty:name>
                  <pdfaProperty:valueType>Text</pdfaProperty:valueType>
                  <pdfaProperty:category>external</pdfaProperty:category>
                  <pdfaProperty:description>name of the embedded XML invoice file</pdfaProperty:description>
                </rdf:li>
                <rdf:li rdf:parseType="Resource">
                  <pdfaProperty:name>DocumentType</pdfaProperty:name>
                  <pdfaProperty:valueType>Text</pdfaProperty:valueType>
                  <pdfaProperty:category>external</pdfaProperty:category>
                  <pdfaProperty:description>INVOICE</pdfaProperty:description>
                </rdf:li>
                <rdf:li rdf:parseType="Resource">
                  <pdfaProperty:name>Version</pdfaProperty:name>
                  <pdfaProperty:valueType>Text</pdfaProperty:valueType>
                  <pdfaProperty:category>external</pdfaProperty:category>
                  <pdfaProperty:description>The actual version of the Factur-X XML schema</pdfaProperty:description>
                </rdf:li>
                <rdf:li rdf:parseType="Resource">
                  <pdfaProperty:name>ConformanceLevel</pdfaProperty:name>
                  <pdfaProperty:valueType>Text</pdfaProperty:valueType>
                  <pdfaProperty:category>external</pdfaProperty:category>
                  <pdfaProperty:description>The conformance level of the embedded Factur-X data</pdfaProperty:description>
                </rdf:li>
              </rdf:Seq>
            </pdfaSchema:property>
          </rdf:li>
        </rdf:Bag>
      </pdfaExtension:schemas>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>"""


def ajouter_output_intent_pdfa(pdf: pikepdf.Pdf) -> None:
    """Déclare le profil de couleur de sortie, requis pour la conformité PDF/A.
    Le profil sRGB est généré en mémoire (littlecms/Pillow) plutôt que lu
    depuis un fichier système, pour rester portable entre distributions."""

    donnees_icc = ImageCms.ImageCmsProfile(ImageCms.createProfile("sRGB")).tobytes()

    flux_icc = pikepdf.Stream(pdf, donnees_icc)
    flux_icc[Name("/N")] = 3  # 3 composantes couleur (RGB)
    flux_icc[Name("/Alternate")] = Name("/DeviceRGB")

    output_intent = pdf.make_indirect(
        Dictionary(
            Type=Name("/OutputIntent"),
            S=Name("/GTS_PDFA1"),
            OutputConditionIdentifier=String("sRGB IEC61966-2.1"),
            Info=String("sRGB IEC61966-2.1"),
            DestOutputProfile=pdf.make_indirect(flux_icc),
        )
    )

    pdf.Root[Name("/OutputIntents")] = Array([output_intent])


def ajouter_reference_af(pdf: pikepdf.Pdf, filespec_obj) -> None:
    """Ajoute la pièce jointe dans le tableau /AF du catalogue, requis par
    la norme PDF/A-3 pour signaler qu'il s'agit d'une pièce jointe normative
    (et pas d'une simple annexe)."""

    pdf.Root[Name("/AF")] = Array([filespec_obj])


def fusionner_facturx(
    chemin_pdf_visuel: str,
    chemin_xml: str,
    chemin_sortie: str,
    numero_facture: str = "",
    profil_urn: str = "urn:cen.eu:en16931:2017",
    niveau_conformite: str = "EN16931",
) -> None:
    with open(chemin_xml, "rb") as f:
        donnees_xml = f.read()

    pdf = pikepdf.open(chemin_pdf_visuel)

    # 1. Attacher le XML CII comme pièce jointe normative
    filespec = pikepdf.AttachedFileSpec(
        pdf,
        donnees_xml,
        description="Factur-X CII invoice XML",
        filename="factur-x.xml",
        mime_type="text/xml",
        relationship=Name("/Data"),
    )
    pdf.attachments["factur-x.xml"] = filespec

    # 2. Référencer la pièce jointe dans /AF (obligatoire PDF/A-3)
    ajouter_reference_af(pdf, filespec.obj)

    # 3. Ajouter le profil de sortie ICC (obligatoire PDF/A)
    ajouter_output_intent_pdfa(pdf)

    # 4. Ecrire les métadonnées XMP (profil Factur-X + conformité PDF/A-3)
    # L'API haut niveau de pikepdf ne permet pas d'injecter facilement des
    # namespaces XMP personnalisés (fx:, pdfaid:) : on écrit donc directement
    # le flux Metadata du catalogue avec le paquet XMP construit à la main.
    xmp = construire_xmp(numero_facture, profil_urn, niveau_conformite, "")
    meta_stream = pikepdf.Stream(pdf, xmp.encode("utf-8"))
    meta_stream[Name("/Type")] = Name("/Metadata")
    meta_stream[Name("/Subtype")] = Name("/XML")
    pdf.Root[Name("/Metadata")] = pdf.make_indirect(meta_stream)

    # 5. Informations du document (recommandé par le standard Factur-X)
    pdf.docinfo[Name("/Title")] = String(f"Facture {numero_facture}")
    pdf.docinfo[Name("/Author")] = String("Facturation")
    pdf.docinfo[Name("/Subject")] = String(f"Facture Factur-X {numero_facture}")
    pdf.docinfo[Name("/Creator")] = String("Script maison Factur-X (pikepdf)")

    pdf.save(chemin_sortie)
    pdf.close()


def main() -> None:
    if len(sys.argv) < 4:
        print(f"Usage : python {sys.argv[0]} <pdf_visuel> <xml_cii> <sortie.pdf>")
        sys.exit(1)

    chemin_pdf, chemin_xml, chemin_sortie = sys.argv[1], sys.argv[2], sys.argv[3]
    fusionner_facturx(chemin_pdf, chemin_xml, chemin_sortie, numero_facture="FA-2026-00123")
    print(f"Fichier Factur-X généré : {chemin_sortie}")


if __name__ == "__main__":
    main()
