#!/usr/bin/env python3
"""Back-upscript voor Roos-Marie (Challenge 3).
Gemaakt door: Ilyas Moerkens en [naam medestudent]

Dit script kopieert nieuwe en gewijzigde bestanden uit de documentenmap
naar een USB-stick. De originele bestanden worden nooit aangepast of
verwijderd. Als er al een oude versie op de stick staat, zetten we die
eerst in de map 'oude_versies'.

Gebruik:  python3 backup.py              (standaardmappen hieronder)
          python3 backup.py <bron> <doel>  (bijv. om te testen)
"""
import shutil
import sys
from datetime import datetime
from pathlib import Path

# Pas deze twee regels aan voor de eigen Raspberry Pi.
BRON = Path.home() / "Documenten"   # map met de documenten
DOEL = Path("/media/pi/BACKUP")     # koppelpunt van de USB-stick

# Tijdelijke bestanden die geen back-up nodig hebben (Word, browsers).
OVERSLAAN_BEGIN = ("~$", ".~lock")
OVERSLAAN_EXTENSIES = {".tmp", ".part", ".crdownload"}

# USB-sticks (FAT32) slaan de tijd op in stappen van 2 seconden.
TIJD_MARGE = 2


class BackupFout(Exception):
    """Fout die de back-up stopt, met een uitleg in gewone taal."""


def overslaan(pad):
    return pad.name.startswith(OVERSLAAN_BEGIN) or pad.suffix.lower() in OVERSLAAN_EXTENSIES


def is_gewijzigd(bron_bestand, backup_bestand):
    if not backup_bestand.exists():
        return True
    b, k = bron_bestand.stat(), backup_bestand.stat()
    return b.st_size != k.st_size or b.st_mtime > k.st_mtime + TIJD_MARGE


def maak_backup(bron, doel):
    """Maakt de back-up en geeft telling terug: gekopieerd, ongewijzigd, fouten."""
    bron, doel = Path(bron).expanduser(), Path(doel).expanduser()

    if not bron.is_dir():
        raise BackupFout(f"De map '{bron}' bestaat niet. Er is niets om te back-uppen.")
    # Bewust geen mkdir: zonder USB-stick zouden we anders op de SD-kaart schrijven.
    if not doel.is_dir():
        raise BackupFout(
            f"De USB-stick is niet gevonden ({doel}). "
            "Steek de stick in; de volgende back-up gaat dan vanzelf goed."
        )
    if doel.resolve().is_relative_to(bron.resolve()):
        raise BackupFout("De back-upplek mag niet in de documentenmap zelf liggen.")

    backup_map = doel / "backup"
    oude_map = doel / "oude_versies" / datetime.now().strftime("%Y%m%d_%H%M%S")
    telling = {"gekopieerd": 0, "ongewijzigd": 0, "fouten": 0}

    for bestand in sorted(bron.rglob("*")):
        if not bestand.is_file() or overslaan(bestand):
            continue
        relatief = bestand.relative_to(bron)
        kopie = backup_map / relatief
        try:
            if not is_gewijzigd(bestand, kopie):
                telling["ongewijzigd"] += 1
                continue
            if kopie.exists():
                # TODO: oude versies worden nog niet opgeruimd, dus de stick kan na lange tijd vol raken.
                (oude_map / relatief).parent.mkdir(parents=True, exist_ok=True)
                shutil.move(kopie, oude_map / relatief)
            kopie.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(bestand, kopie)
            telling["gekopieerd"] += 1
        except OSError as fout:
            # Eén kapot of vergrendeld bestand mag de rest van de back-up niet stoppen.
            telling["fouten"] += 1
            print(f"  Kon '{relatief}' niet kopiëren: {fout}", file=sys.stderr)

    return telling


def main(argumenten):
    bron = argumenten[1] if len(argumenten) > 1 else BRON
    doel = argumenten[2] if len(argumenten) > 2 else DOEL
    tijd = datetime.now().strftime("%d-%m-%Y %H:%M")
    try:
        t = maak_backup(bron, doel)
    except BackupFout as fout:
        print(f"[{tijd}] BACK-UP MISLUKT: {fout}", file=sys.stderr)
        return 1
    print(
        f"[{tijd}] Back-up gelukt: {t['gekopieerd']} nieuw/gewijzigd, "
        f"{t['ongewijzigd']} ongewijzigd, {t['fouten']} fout(en)."
    )
    return 1 if t["fouten"] else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
