"""Contrôle une source CSV et remplace atomiquement la sortie transformée."""

import csv
import hashlib
import os
import sys
import tempfile
from collections import Counter
from datetime import date
from pathlib import Path

COLONNES = ["order_id", "order_status", "date_achat"]


def lire_et_controler(source: Path) -> list[dict[str, str]]:
    with source.open(encoding="utf-8-sig", newline="") as fichier:
        lecteur = csv.DictReader(fichier)
        if lecteur.fieldnames != COLONNES:
            raise ValueError(f"en-tête attendu : {','.join(COLONNES)}")
        lignes = list(lecteur)

    for numero, ligne in enumerate(lignes, 2):
        if None in ligne or any(ligne[colonne] is None for colonne in COLONNES):
            raise ValueError(
                f"enregistrement {numero - 1} : nombre de champs différent de l'en-tête "
                f"({len(COLONNES)} attendus)"
            )

    identifiants = [(ligne["order_id"] or "").strip() for ligne in lignes]
    vides = identifiants.count("")
    doublons = sorted(
        identifiant
        for identifiant, total in Counter(identifiants).items()
        if identifiant and total > 1
    )
    if vides or doublons:
        detail = []
        if vides:
            detail.append(f"{vides} identifiant(s) vide(s)")
        if doublons:
            detail.append("identifiant(s) répété(s) : " + ", ".join(doublons))
        raise ValueError(" ; ".join(detail))

    for numero, ligne in enumerate(lignes, 2):
        try:
            date.fromisoformat(ligne["date_achat"])
        except ValueError as erreur:
            raise ValueError(f"ligne {numero} : date_achat invalide") from erreur
    return lignes


def transformer(lignes: list[dict[str, str]]) -> list[dict[str, str]]:
    return [
        {"order_id": ligne["order_id"], "date_achat": ligne["date_achat"]}
        for ligne in lignes
        if ligne["order_status"] == "delivered"
    ]


def ecrire_atomiquement(destination: Path, lignes: list[dict[str, str]]) -> None:
    destination.parent.mkdir(parents=True, exist_ok=True)
    temporaire = None
    try:
        with tempfile.NamedTemporaryFile(
            mode="w",
            encoding="utf-8",
            newline="",
            dir=destination.parent,
            prefix=destination.name + ".",
            suffix=".tmp",
            delete=False,
        ) as fichier:
            temporaire = Path(fichier.name)
            writer = csv.DictWriter(fichier, fieldnames=["order_id", "date_achat"])
            writer.writeheader()
            writer.writerows(lignes)
        os.replace(temporaire, destination)
    finally:
        if temporaire is not None and temporaire.exists():
            temporaire.unlink()


def empreinte(chemin: Path) -> str:
    return hashlib.sha256(chemin.read_bytes()).hexdigest()


def main() -> int:
    if len(sys.argv) != 3:
        print("Usage : py transferer_commandes.py SOURCE.csv SORTIE.csv")
        return 2
    source, destination = map(Path, sys.argv[1:])
    try:
        # Une source ne doit jamais devenir la destination, même si l'un des
        # deux chemins passe par un lien symbolique.
        if source.resolve() == destination.resolve():
            raise ValueError("la source et la sortie doivent être deux fichiers différents")
        lignes_source = lire_et_controler(source)
        lignes_sortie = transformer(lignes_source)
        ecrire_atomiquement(destination, lignes_sortie)
    except (OSError, ValueError) as erreur:
        print(f"ECHEC : {erreur}")
        if destination.exists():
            print(f"SHA256 SORTIE INCHANGÉE : {empreinte(destination)}")
        else:
            print("SORTIE INCHANGÉE : aucun fichier ne préexistait")
        return 1
    # L'écriture validée est terminée. Une erreur d'affichage ultérieure ne doit
    # pas être présentée comme un rejet de la source avec sortie inchangée.
    print(f"SOURCE : {len(lignes_source)} ligne(s)")
    print(f"SORTIE : {len(lignes_sortie)} ligne(s) retenue(s)")
    print(f"SHA256 : {empreinte(destination)}")
    print("TRANSFERT : OK")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
