"""Analyse locale ClamAV d'un seul fichier ordinaire choisi explicitement.

Ne copie pas le fichier, ne le supprime pas, n'envoie aucune donnée. Sortie : un JSON sur la sortie standard.
Codes de retour : 0 aucune détection rapportée · 1 détection rapportée · 2 autre état (erreur, moteur ou signatures absents, délai dépassé).
"""
import argparse
import datetime
import json
import re
import shutil
import subprocess
from pathlib import Path

DEFAULT_TIMEOUT = 120
STATES = ("no_detection_reported", "detection_reported", "engine_unavailable",
          "signatures_missing", "scan_timeout", "scan_error")

def now_utc():
    return datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds")

def verdict(code, stderr=""):
    if code == 0:
        return "no_detection_reported"
    if code == 1:
        return "detection_reported"
    low = (stderr or "").lower()
    if "no supported database files found" in low or "can't open file or directory" in low or "cvd" in low and "not found" in low:
        return "signatures_missing"
    return "scan_error"

def engine_info(tool):
    """Version du moteur et des signatures, d'après `clamscan --version` (ex. « ClamAV 1.0.5/27000/Mon Nov  8 08:00:00 2026 »)."""
    info = {"engine_version": None, "signature_version": None, "signature_date": None}
    try:
        out = subprocess.run([tool, "--version"], capture_output=True, text=True, timeout=15, check=False).stdout.strip()
    except (OSError, subprocess.TimeoutExpired):
        return info
    parts = out.split("/", 2)
    if parts and parts[0]:
        info["engine_version"] = parts[0].strip()
    if len(parts) >= 2 and re.fullmatch(r"\d+", parts[1].strip()):
        info["signature_version"] = parts[1].strip()
    if len(parts) == 3:
        info["signature_date"] = parts[2].strip()
    return info

def scan(path, timeout=DEFAULT_TIMEOUT):
    """Retourne (état, détails). Lève ValueError si la cible n'est pas un fichier ordinaire."""
    if path.is_symlink() or not path.is_file():
        raise ValueError("Sélectionner un fichier ordinaire autorisé")
    tool = shutil.which("clamscan")
    if tool is None:
        return "engine_unavailable", {}
    info = engine_info(tool)
    try:
        result = subprocess.run(
            [tool, "--no-summary", "--", str(path.resolve())],
            stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True,
            timeout=timeout, check=False,
        )
    except subprocess.TimeoutExpired:
        return "scan_timeout", info
    except OSError:
        return "scan_error", info
    return verdict(result.returncode, result.stderr), info

def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("file", type=Path)
    parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT, help="délai maximal en secondes (défaut 120)")
    args = parser.parse_args(argv)
    try:
        state, info = scan(args.file, args.timeout)
    except ValueError as error:
        parser.error(str(error))
    report = {"schema": 2, "engine": "ClamAV", "scope": "one_file", "observed_at_utc": now_utc(), "state": state}
    report.update(info)
    print(json.dumps(report))
    return 0 if state == "no_detection_reported" else 1 if state == "detection_reported" else 2

if __name__ == "__main__":
    raise SystemExit(main())
