#!/usr/bin/env python3
"""
ReBreak Unsupervised Sideload Profile Generator
================================================
Generiert pro-User .mobileconfig mit frischen UUIDs + individuellem
RemovalPassword. Validiert das Resultat via `plutil -lint`.

KEIN Signing-Step (User-Entscheidung 2026-05-26: unsigned ausliefern).
iOS zeigt "Nicht überprüft" rot beim Install, akzeptabel für DiGA-Pilot.

Usage:
    python3 generate-unsupervised-profile.py \\
        --removal-password 482915 \\
        --org "ReBreak GmbH" \\
        --output /tmp/rebreak-schutz-user-abc123.mobileconfig

    python3 generate-unsupervised-profile.py --batch users.csv --output-dir /tmp/profiles/

CSV-Format für --batch:
    user_id,removal_password
    abc123,482915
    def456,719204
"""

import argparse
import csv
import plistlib
import subprocess
import sys
import uuid
from pathlib import Path

# Template-Quelldatei — wird gelesen, UUIDs + RemovalPassword werden ersetzt.
TEMPLATE_PATH = Path(__file__).parent / "rebreak-iphone-unsupervised-sideload.mobileconfig"


def generate_profile(removal_password: str, org_name: str = "ReBreak") -> bytes:
    """
    Lädt das Template, generiert frische UUIDs für alle PayloadUUID-Felder,
    ersetzt RemovalPassword + PayloadOrganization, returnt finale plist als bytes.
    """
    with open(TEMPLATE_PATH, "rb") as f:
        plist = plistlib.load(f)

    # Top-Level
    plist["PayloadUUID"] = str(uuid.uuid4()).upper()
    plist["PayloadOrganization"] = org_name
    plist["RemovalPassword"] = removal_password

    # Sub-Payloads: jeder kriegt eine frische UUID
    for sub in plist.get("PayloadContent", []):
        sub["PayloadUUID"] = str(uuid.uuid4()).upper()

    return plistlib.dumps(plist, fmt=plistlib.FMT_XML)


def validate_plist(profile_bytes: bytes, path: Path) -> None:
    """
    Validiert via macOS plutil -lint. Schreibt temporär, lintet, löscht.
    Wirft Exception bei Lint-Fehlern.
    """
    path.write_bytes(profile_bytes)
    result = subprocess.run(
        ["plutil", "-lint", str(path)],
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        raise RuntimeError(f"plutil -lint failed: {result.stdout} {result.stderr}")


def write_profile(removal_password: str, org_name: str, output_path: Path) -> None:
    profile = generate_profile(removal_password=removal_password, org_name=org_name)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    validate_plist(profile, output_path)
    print(f"✓ {output_path}  (removal-PIN: {removal_password})")


def batch_from_csv(csv_path: Path, output_dir: Path, org_name: str) -> None:
    with open(csv_path) as f:
        reader = csv.DictReader(f)
        for row in reader:
            user_id = row["user_id"].strip()
            removal_pin = row["removal_password"].strip()
            output_path = output_dir / f"rebreak-schutz-{user_id}.mobileconfig"
            write_profile(removal_pin, org_name, output_path)


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--removal-password", help="6+ digit PIN für Profil-Removal (einzelner User)")
    parser.add_argument("--org", default="ReBreak", help="PayloadOrganization-Name")
    parser.add_argument("--output", help="Output-Pfad (einzelner User)")
    parser.add_argument("--batch", help="CSV-Datei: user_id,removal_password")
    parser.add_argument("--output-dir", help="Output-Verzeichnis für --batch")
    args = parser.parse_args()

    if args.batch:
        if not args.output_dir:
            parser.error("--batch braucht --output-dir")
        batch_from_csv(Path(args.batch), Path(args.output_dir), args.org)
    else:
        if not (args.removal_password and args.output):
            parser.error("--removal-password und --output sind required (oder --batch nutzen)")
        if not args.removal_password.isdigit() or len(args.removal_password) < 6:
            parser.error("--removal-password muss 6+ Ziffern haben")
        write_profile(args.removal_password, args.org, Path(args.output))

    return 0


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