#!/usr/bin/env python3
"""Registra y revierte solo los archivos de una instalacion manual de SummaPack."""

import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys


def fallar(mensaje):
    raise SystemExit(f"manifiesto-instalacion: {mensaje}")


def ruta_logica(valor, destdir, prefix):
    if destdir and (valor == destdir or valor.startswith(destdir + "/")):
        valor = valor[len(destdir):]
    if not valor.startswith("/") or ".." in Path(valor).parts:
        fallar(f"ruta insegura en listado: {valor}")
    if not (valor.startswith(prefix + "/") or valor.startswith("/etc/")):
        fallar(f"ruta fuera de PREFIX y /etc: {valor}")
    return valor


def ruta_fisica(logica, destdir):
    ruta = Path(destdir + logica)
    limite = Path(destdir or "/")
    padre = ruta.parent
    while padre != limite and padre != padre.parent:
        if padre.is_symlink():
            fallar(f"directorio enlazado en destino: {padre}")
        padre = padre.parent
    return ruta


def huella(ruta):
    if ruta.is_symlink():
        return {"tipo": "enlace", "sha256": hashlib.sha256(os.readlink(ruta).encode()).hexdigest()}
    if ruta.is_file():
        return {"tipo": "archivo", "sha256": hashlib.sha256(ruta.read_bytes()).hexdigest()}
    fallar(f"no es un archivo instalado: {ruta}")


def pertenece_a_paquete(logica):
    for orden in (["dpkg-query", "-S", logica], ["rpm", "-qf", logica]):
        if shutil.which(orden[0]) and subprocess.run(orden, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False).returncode == 0:
            return True
    return False


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("modo", choices=("registrar", "desinstalar"))
    parser.add_argument("--prefix", required=True)
    parser.add_argument("--destdir", default="")
    parser.add_argument("--listado", default="listado.ls")
    args = parser.parse_args()
    prefix = args.prefix.rstrip("/")
    destdir = args.destdir.rstrip("/")
    if not prefix.startswith("/") or prefix in ("", "/") or ".." in Path(prefix).parts:
        fallar("PREFIX debe ser absoluto y no puede ser / ni contener ..")
    if destdir and (not destdir.startswith("/") or ".." in Path(destdir).parts):
        fallar("DESTDIR debe ser absoluto y no contener ..")
    manifiesto_logico = prefix + "/share/summapack/install-manifest.json"
    manifiesto = ruta_fisica(manifiesto_logico, destdir)

    if args.modo == "registrar":
        if manifiesto.exists() or manifiesto.is_symlink():
            fallar(f"ya hay un manifiesto: {manifiesto}; desinstalar antes de reinstalar")
        listado = Path(args.listado)
        if not listado.is_file():
            fallar(f"falta {listado}")
        archivos = set()
        directorios = set()
        for linea in listado.read_text().splitlines():
            if linea.startswith("A:"):
                archivos.add(ruta_logica(linea[2:], destdir, prefix))
            elif linea.startswith("D:"):
                # Algunas lineas historicas anotan /var/cache sin DESTDIR.
                valor = linea[2:]
                if destdir and valor.startswith(destdir + "/"):
                    valor = valor[len(destdir):]
                if valor.startswith("/") and ".." not in Path(valor).parts:
                    directorios.add(valor)
        archivos.update((
            prefix + "/bin/distro.sh",
            prefix + "/bin/subir-doc",
            prefix + "/bin/subir-pagina",
            prefix + "/bin/sincronizar-pagina",
            prefix + "/share/summapack/data/subir_pagina.py",
            prefix + "/share/summapack/data/sincronizar_web.py",
            prefix + "/share/summapack/data/paginas-web.json",
            prefix + "/share/doc/summapack/SINCRONIZAR-WEB.md",
        ))
        entradas = []
        for logica in sorted(archivos):
            ruta = ruta_fisica(logica, destdir)
            entradas.append({"ruta": logica, **huella(ruta)})
            padre = Path(logica).parent
            while str(padre) not in (prefix, "/", "/etc"):
                directorios.add(str(padre))
                padre = padre.parent
        manifiesto.parent.mkdir(parents=True, exist_ok=True)
        datos = {"version": 1, "prefix": prefix, "archivos": entradas, "directorios": sorted(directorios)}
        temporal = manifiesto.with_suffix(".nuevo")
        temporal.write_text(json.dumps(datos, indent=2, ensure_ascii=False) + "\n")
        temporal.replace(manifiesto)
        listado.unlink()
        print(f"SummaPack: registrados {len(entradas)} archivos en {manifiesto}")
        return

    if not manifiesto.is_file() or manifiesto.is_symlink():
        fallar(f"falta un manifiesto regular: {manifiesto}; no se borrara nada")
    datos = json.loads(manifiesto.read_text())
    if datos.get("version") != 1 or datos.get("prefix") != prefix:
        fallar("manifiesto incompatible con PREFIX; no se borrara nada")
    if not destdir and pertenece_a_paquete(manifiesto_logico):
        fallar("instalacion administrada por dpkg/rpm: usar el gestor de paquetes")
    for entrada in datos["archivos"]:
        logica = ruta_logica(entrada["ruta"], destdir, prefix)
        ruta = ruta_fisica(logica, destdir)
        if not (ruta.is_file() or ruta.is_symlink()) or huella(ruta) != {"tipo": entrada["tipo"], "sha256": entrada["sha256"]}:
            fallar(f"archivo ausente o modificado: {ruta}; no se borrara nada")
        if not destdir and pertenece_a_paquete(logica):
            fallar(f"archivo administrado por dpkg/rpm: {logica}; no se borrara nada")
    for entrada in datos["archivos"]:
        ruta_fisica(entrada["ruta"], destdir).unlink()
    manifiesto.unlink()
    for logica in sorted(datos["directorios"] + [str(Path(manifiesto_logico).parent)], key=len, reverse=True):
        if logica in (prefix, "/", "/etc"):
            continue
        ruta = ruta_fisica(logica, destdir)
        if ruta.is_dir():
            try:
                ruta.rmdir()
            except OSError:
                pass  # no borrar directorios compartidos o no vacios
    print(f"SummaPack: desinstalados {len(datos['archivos'])} archivos de {prefix}")


if __name__ == "__main__":
    main()
