"""Safe-Trash: Pfade wandern in einen Timestamp-Slot mit Manifest, statt rm.

Restore und Purge sind vom User aus CLI/TUI ansteuerbar.
"""
from __future__ import annotations

import json
import os
import shutil
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path

from taninux.core.paths import trash_dir


@dataclass
class TrashEntry:
    id: str                 # slot directory name
    slot: Path              # absolute path of the slot
    original: Path          # where it came from
    trashed_at: str         # ISO timestamp
    size_bytes: int


def _slot_for(name: str) -> Path:
    ts = datetime.now().strftime("%Y%m%dT%H%M%S")
    base = trash_dir() / f"{ts}-{name}"
    slot = base
    n = 1
    while slot.exists():
        slot = trash_dir().joinpath(f"{ts}-{name}-{n}")
        n += 1
    return slot


def _dir_size(p: Path) -> int:
    if p.is_symlink():
        return 0
    if p.is_file():
        try:
            return p.stat(follow_symlinks=False).st_size
        except OSError:
            return 0
    if not p.is_dir():
        return 0
    total = 0
    for root, _dirs, files in os.walk(p, followlinks=False):
        for f in files:
            try:
                total += os.stat(os.path.join(root, f), follow_symlinks=False).st_size
            except OSError:
                continue
    return total


def move_to_trash(path: Path) -> TrashEntry:
    """Atomically move `path` into a new trash slot. Returns the entry."""
    if not path.exists():
        raise FileNotFoundError(path)

    size = _dir_size(path)
    slot = _slot_for(path.name)
    slot.mkdir(parents=True)

    payload_dir = slot / path.name
    shutil.move(str(path), str(payload_dir))

    manifest = {
        "original": str(path),
        "trashed_at": datetime.now().isoformat(timespec="seconds"),
        "size_bytes": size,
        "payload": path.name,
    }
    (slot / "manifest.json").write_text(json.dumps(manifest, indent=2))

    return TrashEntry(
        id=slot.name,
        slot=slot,
        original=path,
        trashed_at=manifest["trashed_at"],
        size_bytes=size,
    )


def list_trash() -> list[TrashEntry]:
    entries: list[TrashEntry] = []
    for slot in sorted(trash_dir().iterdir()):
        if not slot.is_dir():
            continue
        mf = slot / "manifest.json"
        if not mf.exists():
            continue
        try:
            data = json.loads(mf.read_text())
        except (json.JSONDecodeError, OSError):
            continue
        entries.append(TrashEntry(
            id=slot.name,
            slot=slot,
            original=Path(data["original"]),
            trashed_at=data.get("trashed_at", ""),
            size_bytes=int(data.get("size_bytes", 0)),
        ))
    return entries


def restore(slot_id: str) -> Path:
    slot = trash_dir() / slot_id
    if not slot.is_dir():
        raise FileNotFoundError(slot_id)
    manifest = json.loads((slot / "manifest.json").read_text())
    original = Path(manifest["original"])
    payload = slot / manifest.get("payload", original.name)
    if original.exists():
        raise FileExistsError(f"original path already exists: {original}")
    original.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(payload), str(original))
    (slot / "manifest.json").unlink()
    slot.rmdir()
    return original


def purge(slot_id: str) -> None:
    slot = trash_dir() / slot_id
    if not slot.is_dir():
        raise FileNotFoundError(slot_id)
    shutil.rmtree(slot)
