# -*- coding: utf-8 -*-
"""
Contrôles de fin de génération et fabrication de l'archive.

Deux vérifications seulement, mais celles qui rattrapent les livraisons
ratées : un fichier référencé qui n'existe pas, et un marqueur de gabarit
resté dans une page.
"""

import os
import re
import zipfile

import projets


# src=…, href=…, url(…) — on ne garde que les références locales.
# Les trois formes d'attribut HTML sont acceptées : guillemets doubles,
# simples, ou aucun (valide en HTML5, et le modèle en écrit parfois).
_REFERENCES = re.compile(
    r"""(?:\b(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>`]+))"""
    r"""|url\(\s*(?:"([^"]*)"|'([^']*)'|([^)"'\s]*))\s*\))""",
    re.IGNORECASE,
)
_MARQUEURS = re.compile(r"\[\[.{0,80}?\]\]", re.DOTALL)

_EXTENSIONS_TEXTE = {".html", ".htm", ".css", ".js", ".php", ".md", ".json", ".txt", ".svg"}


def _est_externe(reference):
    reference = reference.strip()
    return (
        not reference
        or reference.startswith(("http://", "https://", "//", "data:", "mailto:", "tel:", "#"))
    )


def verifier(projet_id):
    """Renvoie la liste des anomalies trouvées. Liste vide = rien à signaler."""
    site = projets.dossier_site(projet_id)
    if not os.path.isdir(site):
        return ["Aucun fichier produit."]

    anomalies = []
    presents = set()
    for dossier, _, fichiers in os.walk(site):
        for nom in fichiers:
            presents.add(
                os.path.relpath(os.path.join(dossier, nom), site).replace(os.sep, "/")
            )

    for chemin_relatif in sorted(presents):
        if os.path.splitext(chemin_relatif)[1].lower() not in _EXTENSIONS_TEXTE:
            continue
        complet = os.path.join(site, chemin_relatif)
        try:
            with open(complet, encoding="utf-8", errors="replace") as f:
                contenu = f.read()
        except OSError:
            continue

        for marqueur in set(m.group(0) for m in _MARQUEURS.finditer(contenu)):
            apercu = " ".join(marqueur.split())[:60]
            anomalies.append("%s : marqueur de gabarit resté en place — %s"
                             % (chemin_relatif, apercu))

        if os.path.splitext(chemin_relatif)[1].lower() not in {".html", ".htm", ".css", ".php"}:
            continue

        dossier_courant = os.path.dirname(chemin_relatif)
        manquants = set()
        for correspondance in _REFERENCES.finditer(contenu):
            reference = next((g for g in correspondance.groups() if g), "")
            # On retire l'ancre et la chaîne de requête : elles ne font pas
            # partie du nom de fichier.
            reference = reference.split("#", 1)[0].split("?", 1)[0].strip()
            if _est_externe(reference):
                continue
            cible = reference.lstrip("/") if reference.startswith("/") else \
                os.path.normpath(os.path.join(dossier_courant, reference))
            cible = cible.replace(os.sep, "/")
            if cible not in presents:
                manquants.add(reference)
        for reference in sorted(manquants):
            anomalies.append("%s : référence vers un fichier absent — %s"
                             % (chemin_relatif, reference))

    return anomalies


def archiver(projet_id):
    """Fabrique livraison.zip à partir du dossier site/."""
    site = projets.dossier_site(projet_id)
    archive = projets.chemin_archive(projet_id)
    temporaire = archive + ".tmp"

    with zipfile.ZipFile(temporaire, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
        for dossier, _, fichiers in os.walk(site):
            for nom in sorted(fichiers):
                complet = os.path.join(dossier, nom)
                zf.write(complet, os.path.relpath(complet, site))
    os.replace(temporaire, archive)
    return archive
