"""Gestión local del ciclo de vida de artefactos temporales del módulo batch."""

from __future__ import annotations

import re
from pathlib import Path


class LocalBatchArtifactCleaner:
    """Elimina artefactos temporales y poda directorios vacíos propios del módulo."""

    def __init__(self, base_dir: Path) -> None:
        self.archive_root = Path(base_dir) / "_batch_uploads"
        self.extracted_root = Path(base_dir) / "_batch_extracted"
        self.report_root = Path(base_dir) / "_batch_reports"
        self.archive_root.mkdir(parents=True, exist_ok=True)
        self.extracted_root.mkdir(parents=True, exist_ok=True)
        self.report_root.mkdir(parents=True, exist_ok=True)

    def delete_archive(self, archive_path: str) -> None:
        """Elimina el ZIP persistido del lote y limpia directorios vacíos."""

        self._delete_file(Path(archive_path), owned_root=self.archive_root)

    def delete_extracted_file(self, file_path: str) -> None:
        """Elimina un PDF temporal extraído y limpia directorios vacíos."""

        self._delete_file(Path(file_path), owned_root=self.extracted_root)

    def _delete_file(self, target: Path, *, owned_root: Path) -> None:
        if not str(target or "").strip():
            return

        resolved_target = target.resolve(strict=False)
        resolved_root = owned_root.resolve(strict=False)
        if not resolved_target.is_relative_to(resolved_root):
            raise ValueError(f"Ruta fuera del directorio administrado: {target}")

        if target.is_symlink() or target.exists():
            if target.is_dir():
                raise IsADirectoryError(f"Se esperaba un archivo, no un directorio: {target}")
            target.unlink()
        else:
            return

        self._prune_empty_dirs(target.parent, stop_at=owned_root)

    def _prune_empty_dirs(self, current: Path, *, stop_at: Path) -> None:
        resolved_stop = stop_at.resolve(strict=False)
        candidate = current.resolve(strict=False)
        while candidate != resolved_stop:
            if not candidate.is_relative_to(resolved_stop):
                return
            try:
                next(candidate.iterdir())
                return
            except StopIteration:
                candidate.rmdir()
                candidate = candidate.parent


class LocalBatchWorkingFileStore:
    """Guarda PDFs individuales en el área temporal que consume el pipeline batch."""

    def __init__(self, base_dir: Path) -> None:
        self.base_dir = Path(base_dir) / "_batch_extracted"
        self.base_dir.mkdir(parents=True, exist_ok=True)

    def save_file(self, batch_id: str, filename: str, data: bytes) -> str:
        safe_name = self._safe_filename(filename)
        batch_dir = self.base_dir / batch_id / "manual"
        batch_dir.mkdir(parents=True, exist_ok=True)
        target = batch_dir / safe_name
        target.write_bytes(data)
        return str(target)

    def _safe_filename(self, filename: str) -> str:
        raw = Path(str(filename or "").strip()).name or "documento.pdf"
        stem = re.sub(r"[^A-Za-z0-9._-]+", "_", Path(raw).stem).strip("._-") or "documento"
        suffix = Path(raw).suffix.lower() or ".pdf"
        if suffix != ".pdf":
            suffix = ".pdf"
        return f"{stem}{suffix}"


class LocalBatchExcelReportStore:
    """Guarda reportes Excel del módulo batch en un directorio local aislado."""

    def __init__(self, base_dir: Path) -> None:
        self.base_dir = Path(base_dir) / "_batch_reports"
        self.base_dir.mkdir(parents=True, exist_ok=True)

    def save_excel_report(self, batch_id: str, filename: str, data: bytes) -> str:
        batch_dir = self.base_dir / batch_id
        batch_dir.mkdir(parents=True, exist_ok=True)
        report_path = batch_dir / filename
        report_path.write_bytes(data)
        return str(report_path)
