import os
from pathlib import Path

from PyInstaller.utils.hooks import collect_all, copy_metadata

# Spec CLI frozen — EXE de PRODUCTION (anonymisation fichier unique sans GUI).
# Même moteur / mêmes datas que anonymisation_onefile.spec, mais :
#   - entrypoint = scripts/anonymize_cli.py (CLI production, pas launcher.py)
#     Contrat : Anonymisation-CLI.exe <fichier> <dossier_sortie>
#     Modèle CamemBERT-bio ONNX OBLIGATOIRE (fail-closed, code 3 si absent).
#   - console=True (CLI), pas de Splash
#   - name = Anonymisation-CLI  -> ne remplace pas dist/Anonymisation.exe
# (Le harnais perf D-19 reste scripts/anonymize_batch_cli.py, non buildé ici.)

block_cipher = None

project_dir = Path(globals().get("SPECPATH", os.getcwd())).resolve()


def _data_entry(relative_path: str, target_dir: str | None = None):
    src = project_dir / relative_path
    if not src.exists():
        return None
    return (str(src), target_dir or relative_path)


binaries = []
datas = []
for relative_path, target_dir in [
    ("config", "config"),
    ("data/bdpm", "data/bdpm"),
    ("data/edsnlp", "data/edsnlp"),
    ("data/finess", "data/finess"),
    ("data/insee", "data/insee"),
    ("models/camembert-bio-deid/onnx", "models/camembert-bio-deid/onnx"),
    ("detectors", "detectors"),
    ("scripts", "scripts"),
    ("assets", "assets"),
]:
    entry = _data_entry(relative_path, target_dir)
    if entry is not None:
        datas.append(entry)

for relative_path in [
    "data/stopwords_manuels.txt",
    "data/villes_blacklist.txt",
    "data/dpi_labels_blacklist.txt",
    "data/companion_blacklist.txt",
]:
    entry = _data_entry(relative_path, "data")
    if entry is not None:
        datas.append(entry)

onnxtr_cache_dir = Path(os.environ.get("ONNXTR_CACHE_DIR", Path.home() / ".cache" / "onnxtr"))
required_onnxtr_weights = [
    "db_resnet50-69ba0015.onnx",
    "crnn_vgg16_bn-743599aa.onnx",
]
missing_onnxtr_weights = []
for filename in required_onnxtr_weights:
    src = onnxtr_cache_dir / "models" / filename
    if src.exists():
        datas.append((str(src), "models/onnxtr/models"))
    else:
        missing_onnxtr_weights.append(str(src))
if missing_onnxtr_weights:
    raise FileNotFoundError(
        "Poids OCR OnnxTR manquants pour le build frozen : "
        + ", ".join(missing_onnxtr_weights)
        + ". Précharger OnnxTR (lancer une OCR une fois) ou définir ONNXTR_CACHE_DIR avant PyInstaller."
    )


hiddenimports = [
    "anonymizer_core_refactored_onnx",
    "admin_rules",
    "config_defaults",
    "profile_defaults",
    "gui_batch_paths",
    "manual_masking",
    "pdf_mask_designer",
    "format_converter",
    "ner_manager_onnx",
    "camembert_ner_manager",
    "eds_pseudo_manager",
    "gliner_manager",
    "vlm_manager",
    "build_info",
    # OCR OnnxTR (ONNX Runtime, remplace docTR — sans torch ni doctr)
    "onnxtr",
    "onnxtr.io",
    "onnxtr.models",
    "onnxtr.models.detection",
    "onnxtr.models.recognition",
    "onnxtr.utils",
    "onnxtr.utils.data",
    # Dépendances transitives OnnxTR (hiddenimports défensifs vs omission PyInstaller)
    "pyclipper",
    "scipy.cluster.hierarchy",
    "scipy.special",
    "cv2",
    "edsnlp",
    "edsnlp.pipes",
    "edsnlp.pipes.ner",
    "edsnlp.pipes.ner.pseudo",
    "spacy",
    "spacy.lang.fr",
    "gliner",
    "onnxruntime",
    "transformers",
    "tokenizers",
    "pdfplumber",
    "fitz",
    "PIL",
    "yaml",
    "loguru",
    "regex",
]


def _collect_optional_package(package_name: str):
    try:
        package_datas, package_binaries, package_hiddenimports = collect_all(package_name)
        datas.extend(package_datas)
        binaries.extend(package_binaries)
        hiddenimports.extend(package_hiddenimports)
        try:
            datas.extend(copy_metadata(package_name, recursive=True))
        except Exception:
            pass
    except Exception:
        pass


for _package_name in [
    "edsnlp",
    "spacy",
    "thinc",
    "blis",
    "srsly",
    "catalogue",
    "confection",
    "cymem",
    "preshed",
    "murmurhash",
    "gliner",
    "loguru",
]:
    _collect_optional_package(_package_name)


# P0-3 (Plan 3) : exclusion dure de la pile torch. Le core fait un
# `import torch` lazy (try/except no-op) dans _configure_torch_threads que
# l'analyse statique suivrait ; en frozen l'ImportError est attendue et gérée.
EXCLUDED_TORCH_STACK = [
    "torch",
    "torchvision",
    "optimum",
    "doctr",
]


a = Analysis(
    [str(project_dir / "scripts" / "anonymize_cli.py")],
    pathex=[str(project_dir)],
    binaries=binaries,
    datas=datas,
    hiddenimports=hiddenimports,
    excludes=EXCLUDED_TORCH_STACK,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name="Anonymisation-CLI",
    debug=False,
    strip=False,
    upx=False,
    console=True,
)
