"""Redacted support bundle generation for bug reports."""

from __future__ import annotations

import datetime as _dt
import importlib.metadata
import json
import os
import platform
import shutil
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path
from typing import Any, Iterable, Mapping, Optional

from backend.cache_inventory import discover_caches, model_cache_status
from backend.crash_reporter import _path_scrub
from backend.dependency_caps import (
    collect_onnxruntime_provider_status,
    collect_opencv_wheel_status,
    collect_rapidocr_engine_status,
)
from backend.ffmpeg_profiles import (
    collect_ffmpeg_capability_profiles,
    ffmpeg_profile_entries,
    probe_ffmpeg_security,
)
from backend.model_downloads import (
    installed_backend_status,
    optional_outbound_model_paths,
    recorded_model_provenance,
)
from backend.opencv_ocr import collect_opencv_dnn_ocr_status
from backend.security_checks import (
    FFMPEG_SECURITY_ADVISORY_IDS,
    FFMPEG_SECURITY_ADVISORY_URL,
    cpython_security_status,
    opencv_libpng_status,
)
from backend.subprocess_policy import run_process


SUPPORT_BUNDLE_SCHEMA = "vsr.support_bundle.v1"
MAX_LOG_BYTES = 256 * 1024
_DEPENDENCY_PACKAGES = (
    "numpy",
    "opencv-python",
    "opencv-contrib-python",
    "Pillow",
    "torch",
    "torchvision",
    "onnxruntime",
    "onnxruntime-gpu",
    "onnxruntime-directml",
    "openvino",
    "paddleocr",
    "rapidocr",
    "rapidocr-onnxruntime",
    "easyocr",
    "sentry-sdk",
)
_SENSITIVE_KEYS = {
    "abs_path",
    "dsn",
    "file_path",
    "filename",
    "input",
    "input_path",
    "log_file",
    "model_path",
    "output",
    "output_path",
    "password",
    "path",
    "settings_file",
    "token",
    "window_geometry",
    "work_directory",
    "cachepath",
}
_SENSITIVE_SUFFIXES = (
    "_dir",
    "_directory",
    "_dsn",
    "_file",
    "_folder",
    "_path",
    "_secret",
    "_token",
)
_ENV_FLAG_KEYS = (
    "VSR_ALLOW_GPL",
    "VSR_ALLOW_REMOTE_CODE",
    "VSR_ALLOW_REMOTE_VLM",
    "VSR_CRASH_REPORTS",
    "VSR_DISABLE_UPDATE_CHECK",
    "VSR_FORCE_CPU",
    "VSR_VACE_AUTO_FETCH",
    "VSR_ALLOW_UNVERIFIED_MODELS",
)
_TRUE_FLAG_VALUES = {"1", "true", "yes", "on"}


def default_appdata_root() -> Path:
    return (
        Path(os.environ.get("APPDATA", Path.home() / ".config"))
        / "VideoSubtitleRemoverPro"
    )


def default_settings_path() -> Path:
    return default_appdata_root() / "settings.json"


def default_log_path() -> Path:
    return default_appdata_root() / "vsr_pro.log"


def create_support_bundle(
    output_path: str | Path,
    *,
    settings_path: Optional[str | Path] = None,
    log_path: Optional[str | Path] = None,
    batch_report_paths: Optional[Iterable[str | Path]] = None,
    app_version: str = "",
    extra_facts: Optional[Mapping[str, Any]] = None,
) -> Path:
    """Write a zip file with redacted diagnostics and return its path."""
    output = Path(output_path)
    if output.suffix.lower() != ".zip":
        output = output.with_suffix(".zip")
    output.parent.mkdir(parents=True, exist_ok=True)
    settings = Path(settings_path) if settings_path else default_settings_path()
    log = Path(log_path) if log_path else default_log_path()
    reports = [Path(p) for p in (batch_report_paths or [])]
    included: list[str] = []

    fd, temp_name = tempfile.mkstemp(
        prefix=f".{output.stem}.",
        suffix=".tmp",
        dir=str(output.parent),
    )
    os.close(fd)
    temp_path = Path(temp_name)
    try:
        with zipfile.ZipFile(
            temp_path,
            "w",
            compression=zipfile.ZIP_DEFLATED,
        ) as bundle:
            support_payload = _support_payload(
                app_version=app_version,
                extra_facts=extra_facts or {},
            )
            # Redact the whole payload, not just `facts`. The dependency and
            # provider diagnostics embed absolute install paths (cv2.__file__,
            # ONNX Runtime DLL load errors), so the largest artifact in a
            # bundle users are told to attach to bug reports was the one
            # carrying their Windows username and directory layout.
            _write_json(
                bundle, "support.json", _redact_json(support_payload), included
            )

            settings_payload = _read_json(settings)
            if settings_payload is not None:
                _write_json(
                    bundle,
                    "settings.redacted.json",
                    _redact_json(settings_payload),
                    included,
                )

            log_text = _read_tail_text(log, MAX_LOG_BYTES)
            if log_text is not None:
                _write_text(bundle, "vsr_pro.redacted.log", log_text, included)

            # RM-286: an FFmpeg failure is the most opaque error class in the
            # app. The invocations go in as runnable lines so a maintainer
            # can reproduce one without reconstructing the argv.
            commands_text = ffmpeg_command_log_text()
            if commands_text:
                _write_text(
                    bundle,
                    "ffmpeg-commands.redacted.txt",
                    commands_text,
                    included,
                )

            for index, report_path in enumerate(_unique_existing(reports), 1):
                _write_redacted_report(bundle, report_path, index, included)

        os.replace(temp_path, output)
    finally:
        if temp_path.exists():
            try:
                temp_path.unlink()
            except OSError:
                pass
    return output


def _support_payload(*, app_version: str,
                     extra_facts: Mapping[str, Any]) -> dict:
    return {
        "schema": SUPPORT_BUNDLE_SCHEMA,
        "created_at": _dt.datetime.now(_dt.timezone.utc).isoformat(),
        "app_version": app_version or "unknown",
        "runtime": {
            "python_version": sys.version.split()[0],
            "platform": platform.platform(),
            "system": platform.system(),
            "release": platform.release(),
            "machine": platform.machine(),
            "frozen": bool(getattr(sys, "frozen", False)),
        },
        "env_flags": {
            key: str(os.environ.get(key, "")).strip().lower()
            in _TRUE_FLAG_VALUES
            for key in _ENV_FLAG_KEYS
        },
        "tools": {
            "ffmpeg": _tool_version("ffmpeg"),
            "ffprobe": _tool_version("ffprobe"),
        },
        "ffmpeg_profiles": collect_ffmpeg_capability_profiles(),
        "dependencies": _dependency_versions(),
        "dependency_diagnostics": {
            "opencv": collect_opencv_wheel_status(),
            "opencv_dnn_ocr": collect_opencv_dnn_ocr_status(),
            "onnxruntime": collect_onnxruntime_provider_status(),
        },
        "backend_status": installed_backend_status(),
        "model_provenance": recorded_model_provenance(),
        "optional_outbound_models": list(optional_outbound_model_paths()),
        "model_cache": model_cache_status(),
        "security": {
            "opencv_libpng": opencv_libpng_status(),
            "ffmpeg_runtime": probe_ffmpeg_security(),
        },
        "caches": _cache_summary(),
        "facts": _redact_json(dict(extra_facts)),
    }


def _dependency_versions() -> dict:
    versions = {}
    for package in _DEPENDENCY_PACKAGES:
        try:
            versions[package] = importlib.metadata.version(package)
        except importlib.metadata.PackageNotFoundError:
            versions[package] = None
    return versions


def _tool_version(executable: str) -> dict:
    if shutil.which(executable) is None:
        return {"available": False, "version": None}
    try:
        result = run_process(
            [executable, "-version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
    except (OSError, subprocess.TimeoutExpired):
        return {"available": True, "version": None}
    first_line = (result.stdout or result.stderr).splitlines()[:1]
    return {
        "available": True,
        "version": redact_text(first_line[0]) if first_line else None,
    }


def _cache_summary() -> list[dict]:
    summary = []
    for entry in discover_caches():
        summary.append({
            "label": entry.label,
            "exists": entry.exists,
            "total_bytes": int(entry.total_bytes),
            "file_count": int(entry.file_count),
        })
    return summary


def _unique_existing(paths: Iterable[Path],
                     allowed_suffixes: tuple = (".json", ".md", ".txt",
                                                ".log"),
                     ) -> list[Path]:
    seen = set()
    result = []
    for path in paths:
        try:
            resolved = path.resolve(strict=True)
        except OSError:
            continue
        if resolved.suffix.lower() not in allowed_suffixes:
            continue
        key = str(resolved).casefold()
        if key in seen or not resolved.is_file():
            continue
        seen.add(key)
        result.append(resolved)
    return result


def _write_redacted_report(bundle: zipfile.ZipFile, path: Path,
                           index: int, included: list[str]) -> None:
    suffix = path.suffix.lower()
    if suffix == ".json":
        payload = _read_json(path)
        if payload is None:
            return
        _write_json(
            bundle,
            f"batch-report-{index}.redacted.json",
            _redact_json(payload),
            included,
        )
        return
    text = _read_tail_text(path, MAX_LOG_BYTES)
    if text is None:
        return
    ext = ".md" if suffix == ".md" else ".txt"
    _write_text(bundle, f"batch-report-{index}.redacted{ext}", text, included)


def _write_json(bundle: zipfile.ZipFile, arcname: str,
                payload: Any, included: list[str]) -> None:
    _write_text(
        bundle,
        arcname,
        json.dumps(payload, indent=2, sort_keys=True) + "\n",
        included,
    )


def _write_text(bundle: zipfile.ZipFile, arcname: str,
                text: str, included: list[str]) -> None:
    bundle.writestr(arcname, text)
    included.append(arcname)


def _read_json(path: Path) -> Optional[Any]:
    try:
        with path.open("r", encoding="utf-8") as handle:
            return json.load(handle)
    except (OSError, json.JSONDecodeError):
        return None


def _read_tail_text(path: Path, max_bytes: int) -> Optional[str]:
    try:
        size = path.stat().st_size
        with path.open("rb") as handle:
            start = max(0, size - max_bytes)
            handle.seek(start)
            raw = handle.read(max_bytes)
    except OSError:
        return None
    prefix = "[truncated to last %d bytes]\n" % max_bytes if size > max_bytes else ""
    return prefix + redact_text(raw.decode("utf-8", errors="replace"))


def ffmpeg_command_log_text() -> str:
    """Render the recent FFmpeg invocations as redacted runnable lines."""
    from backend.subprocess_policy import recent_ffmpeg_commands

    entries = recent_ffmpeg_commands()
    if not entries:
        return ""
    lines = [
        "# Recent FFmpeg / ffprobe invocations, oldest first.",
        "# Paths are reduced to file names by the same redaction policy as "
        "the log, so a line may need its directories restored before it runs.",
        "",
    ]
    for entry in entries:
        stamp = _dt.datetime.fromtimestamp(
            float(entry["timestamp"]), _dt.timezone.utc
        ).isoformat(timespec="seconds")
        lines.append(f"# {stamp}")
        lines.append(redact_text(str(entry["command"])))
        lines.append("")
    return "\n".join(lines)


def redact_text(text: str) -> str:
    # The support bundle strips the directory tree but keeps the leaf name --
    # the user reads this archive before sharing it, and a log line without
    # the filename is not actionable. Crash telemetry uses the stricter mode.
    return _path_scrub(text, keep_basename=True)


def _is_sensitive_key(key: str) -> bool:
    lower = key.strip().lower()
    return lower in _SENSITIVE_KEYS or lower.endswith(_SENSITIVE_SUFFIXES)


def _redact_json(value: Any, key: str = "") -> Any:
    if key and _is_sensitive_key(key):
        return "<redacted>"
    if isinstance(value, dict):
        return {str(k): _redact_json(v, str(k)) for k, v in value.items()}
    if isinstance(value, list):
        return [_redact_json(item) for item in value]
    if isinstance(value, str):
        return redact_text(value)
    return value


def run_self_test() -> dict:
    """Probe OCR engines, inpaint backends, GPU providers, and codecs.

    Returns a dict of category -> list of {name, available, reason} entries.
    """
    results = {"ocr": [], "inpaint": [], "gpu": [], "codec": [],
               "dependency": [], "security": [],
               "ffmpeg_profiles": []}

    def _probe(category, name, fn):
        try:
            ok, reason = fn()
            results[category].append({"name": name, "available": ok,
                                       "reason": reason})
        except Exception as exc:
            results[category].append({"name": name, "available": False,
                                       "reason": str(exc)[:200]})

    def _check_rapidocr():
        try:
            from rapidocr import RapidOCR
            RapidOCR()
            status = collect_rapidocr_engine_status()
            provider = status.get("preferredProvider") or "ONNX Runtime"
            return True, f"rapidocr loaded ({provider})"
        except ImportError:
            try:
                from rapidocr_onnxruntime import RapidOCR
                return True, "rapidocr_onnxruntime loaded"
            except ImportError:
                return False, "rapidocr not installed"

    def _check_paddleocr():
        try:
            import paddleocr
            return True, f"paddleocr {getattr(paddleocr, '__version__', '?')}"
        except ImportError:
            return False, "paddleocr not installed"

    def _check_lama_onnx():
        try:
            import onnxruntime
            return True, f"onnxruntime {onnxruntime.__version__}"
        except ImportError:
            return False, "onnxruntime not installed"

    def _check_lama_cv2dnn():
        try:
            import cv2
            major = int(cv2.__version__.split(".")[0])
            if major >= 5:
                return True, f"opencv {cv2.__version__} (DNN available)"
            return False, f"opencv {cv2.__version__} (DNN requires 5.0+)"
        except Exception:
            return False, "opencv not available"

    def _check_cuda():
        try:
            import torch
            if torch.cuda.is_available():
                name = torch.cuda.get_device_name(0)
                return True, f"CUDA: {name}"
            return False, "CUDA not available"
        except ImportError:
            return False, "torch not installed"

    def _check_directml():
        try:
            import onnxruntime as ort
            if "DmlExecutionProvider" in ort.get_available_providers():
                return True, "DirectML available"
            return False, "DmlExecutionProvider not in providers"
        except ImportError:
            return False, "onnxruntime not installed"

    def _check_ffmpeg_encoder(codec_name, encoder_name):
        def _fn():
            import shutil as _sh
            if _sh.which("ffmpeg") is None:
                return False, "ffmpeg not on PATH"
            try:
                # RM-142: every probe goes through the shared bounded policy so
                # it stays hidden on Windows, capped, and cancellable.
                result = run_process(
                    ["ffmpeg", "-hide_banner", "-encoders"],
                    capture_output=True, text=True, timeout=10.0)
                if encoder_name in (result.stdout or ""):
                    return True, f"{encoder_name} available"
                return False, f"{encoder_name} not found in ffmpeg"
            except Exception as e:
                return False, str(e)[:100]
        return _fn

    _probe("ocr", "RapidOCR", _check_rapidocr)
    _probe("ocr", "PaddleOCR", _check_paddleocr)
    _probe("inpaint", "LaMa-ONNX", _check_lama_onnx)
    _probe("inpaint", "LaMa-CV2DNN", _check_lama_cv2dnn)
    _probe("gpu", "CUDA", _check_cuda)
    _probe("gpu", "DirectML", _check_directml)
    _probe("codec", "H.264", _check_ffmpeg_encoder("h264", "libx264"))
    _probe("codec", "H.265", _check_ffmpeg_encoder("h265", "libx265"))
    _probe("codec", "AV1", _check_ffmpeg_encoder("av1", "libsvtav1"))
    _probe("codec", "VVC", _check_ffmpeg_encoder("vvc", "libvvenc"))
    _probe("codec", "NVENC", _check_ffmpeg_encoder("nvenc", "h264_nvenc"))
    opencv_status = collect_opencv_wheel_status()
    opencv_warnings = list(opencv_status.get("warnings", []) or [])
    imported = opencv_status.get("imported", {})
    owner = imported.get("owner") if isinstance(imported, Mapping) else ""
    version = imported.get("version") if isinstance(imported, Mapping) else ""
    file_path = imported.get("file") if isinstance(imported, Mapping) else ""
    reason = (
        str(opencv_warnings[0].get("message"))
        if opencv_warnings else
        f"cv2 {version or 'unknown'} from {owner or 'unknown'} at {file_path or 'unknown'}"
    )
    results["dependency"].append({
        "name": "OpenCV wheel ownership",
        "available": bool(imported.get("available")) and not opencv_warnings
        if isinstance(imported, Mapping) else False,
        "reason": reason,
    })
    try:
        ffmpeg_security = probe_ffmpeg_security()
        if not ffmpeg_security.get("available"):
            available = False
            reason = "ffmpeg not on PATH"
        elif ffmpeg_security.get("classification") not in (None, "safe"):
            available = False
            reason = (
                ffmpeg_security.get("reason")
                or "FFmpeg runtime is not on a reviewed safe branch"
            )
        elif ffmpeg_security.get("vulnerable"):
            available = False
            reason = ffmpeg_security.get("reason") or "vulnerable FFmpeg runtime"
            advisory_ids = list(
                ffmpeg_security.get("advisories") or FFMPEG_SECURITY_ADVISORY_IDS
            )
            missing_ids = [item for item in advisory_ids if str(item) not in reason]
            if missing_ids or FFMPEG_SECURITY_ADVISORY_URL not in reason:
                details = ", ".join(str(item) for item in missing_ids)
                if details:
                    details += "; "
                reason = (
                    f"{reason} ({details}"
                    f"see {FFMPEG_SECURITY_ADVISORY_URL})"
                )
        else:
            available = True
            reason = (
                f"ffmpeg {ffmpeg_security.get('version') or 'unknown'} "
                "(reviewed security branch and patch floor)"
            )
        results["security"].append({
            "name": "FFmpeg runtime CVE floor",
            "available": available,
            "reason": reason,
        })
    except Exception as exc:
        results["security"].append({
            "name": "FFmpeg runtime CVE floor",
            "available": False,
            "reason": str(exc)[:200],
        })

    try:
        from backend.dependency_caps import (
            collect_onnxruntime_provider_status,
            onnxruntime_release_advisories,
        )
        ort_status = collect_onnxruntime_provider_status()
        floor = [
            a for a in onnxruntime_release_advisories(ort_status)
            if a.get("id") == "ORT-OOB-OVERFLOW-1.26.0"
        ]
        if floor:
            results["security"].append({
                "name": "ONNX Runtime CVE floor",
                "available": False,
                "reason": floor[0].get("mitigation")
                or "onnxruntime below the 1.26.0 security floor",
            })
        else:
            results["security"].append({
                "name": "ONNX Runtime CVE floor",
                "available": True,
                "reason": "onnxruntime >= 1.26.0 or not installed",
            })
    except Exception as exc:
        results["security"].append({
            "name": "ONNX Runtime CVE floor",
            "available": False,
            "reason": str(exc)[:200],
        })

    try:
        results["ffmpeg_profiles"] = ffmpeg_profile_entries(
            collect_ffmpeg_capability_profiles()
        )
    except Exception as exc:
        results["ffmpeg_profiles"].append({
            "name": "profiles",
            "available": False,
            "reason": str(exc)[:200],
        })

    cpython_status = cpython_security_status()
    results["security"].append({
        "name": "CPython security floor",
        "available": bool(cpython_status["safe"]),
        "reason": cpython_status["reason"],
    })

    try:
        from backend.ocr_vlm import vlm_endpoint_privacy_status
        vlm_privacy = vlm_endpoint_privacy_status()
        results["security"].append({
            "name": "VLM endpoint privacy",
            "available": bool(vlm_privacy.get("allowed")),
            "reason": str(vlm_privacy.get("message") or "unknown"),
        })
    except Exception as exc:
        results["security"].append({
            "name": "VLM endpoint privacy",
            "available": False,
            "reason": str(exc)[:200],
        })

    try:
        from backend.device_provider import windowsml_status
        wml = windowsml_status()
        results["security"].append({
            "name": "Windows ML forward path",
            "available": wml["available"],
            "reason": (
                f"onnxruntime-windowsml {wml['version']}"
                if wml["available"]
                else "onnxruntime-windowsml not installed; DirectML "
                     "is in maintenance mode"
            ),
        })
    except Exception:
        pass

    return results


INFERENCE_SMOKE_SCHEMA = "vsr.inference_smoke.v1"


def _smoke_text_image():
    """Generate a deterministic white image with black text for OCR smoke."""
    import cv2
    import numpy as np
    img = np.full((96, 384, 3), 245, dtype=np.uint8)
    cv2.putText(img, "SUBTITLE 12", (14, 60), cv2.FONT_HERSHEY_SIMPLEX,
                1.4, (10, 10, 10), 3, cv2.LINE_AA)
    return img


def _smoke_masked_frame():
    """Generate a smooth frame plus a rectangular mask for inpaint smoke."""
    import cv2
    import numpy as np
    yy, xx = np.mgrid[0:96, 0:384]
    frame = np.stack([(xx % 256), (yy * 2 % 256), ((xx + yy) % 256)], -1)
    frame = cv2.GaussianBlur(frame.astype(np.uint8), (9, 9), 0)
    mask = np.zeros((96, 384), np.uint8)
    mask[40:70, 60:320] = 255
    return frame, mask


def run_inference_smoke(*, device: str = "cpu", lang: str = "en") -> dict:
    """Prove that the selected OCR and inpaint backends actually execute.

    Runs a generated text image through the detector and a generated masked
    frame through each locally ready inpaint path, recording the actual engine
    / execution provider, whether inference ran, timing, and a precise skip
    reason when a backend is not installed. No model weights are downloaded:
    OCR uses the wheel-bundled models and inpaint uses local LaMa weights or
    the OpenCV fallback.
    """
    from time import perf_counter

    results: dict = {"schema": INFERENCE_SMOKE_SCHEMA, "device": device,
                     "ocr": [], "inpaint": []}

    # --- OCR execution ---
    try:
        from backend.detection import SubtitleDetector
        image = _smoke_text_image()
        start = perf_counter()
        detector = SubtitleDetector(device=device, lang=lang)
        boxes = detector.detect(image, 0.3)
        elapsed = (perf_counter() - start) * 1000.0
        engine = getattr(detector, "_engine_name", "unknown")
        ran = engine not in ("none", "unknown", None)
        # RM-147: record the requested vs. effective device, not just a label.
        stage = None
        collect = getattr(detector, "execution_provenance", None)
        if callable(collect):
            try:
                stage = collect().to_dict()
            except Exception:
                stage = None
        results["ocr"].append({
            "name": "detector",
            "ran": ran,
            "passed": bool(ran and boxes is not None),
            "provider": str(engine),
            "boxes": len(boxes) if boxes is not None else 0,
            "ms": round(elapsed, 1),
            "reason": "" if ran else "no OCR engine available (weights absent)",
            "requestedDevice": device,
            "effectiveDevice": (
                stage.get("effectiveDevice") if isinstance(stage, dict) else ""),
            "fellBack": (
                stage.get("fellBack") if isinstance(stage, dict) else None),
            "provenance": stage,
        })
    except Exception as exc:  # noqa: BLE001 - smoke must never raise
        results["ocr"].append({
            "name": "detector", "ran": False, "passed": False,
            "provider": "", "boxes": 0, "ms": None, "reason": str(exc)[:200],
        })

    # --- inpaint execution ---
    frame, mask = _smoke_masked_frame()
    try:
        from backend.inpainters import _cv2_inpaint
        import cv2
        start = perf_counter()
        out = _cv2_inpaint(frame, mask, 5, cv2.INPAINT_TELEA)
        elapsed = (perf_counter() - start) * 1000.0
        results["inpaint"].append({
            "name": "cv2", "ran": True,
            "passed": out is not None and out.shape == frame.shape,
            "provider": "opencv", "ms": round(elapsed, 1), "reason": "",
        })
    except Exception as exc:  # noqa: BLE001
        results["inpaint"].append({
            "name": "cv2", "ran": False, "passed": False,
            "provider": "", "ms": None, "reason": str(exc)[:200],
        })

    try:
        from backend.config import ProcessingConfig
        from backend.inpainters import LAMAInpainter
        inpainter = LAMAInpainter(device, ProcessingConfig())
        start = perf_counter()
        cleaned = inpainter.inpaint([frame], [mask])
        elapsed = (perf_counter() - start) * 1000.0
        backend_name = getattr(inpainter, "_backend_name", "unknown")
        ok = bool(cleaned and cleaned[0] is not None
                  and cleaned[0].shape == frame.shape)
        from backend.execution_provenance import device_from_provider
        effective = device_from_provider(str(backend_name))
        results["inpaint"].append({
            "name": "lama", "ran": True, "passed": ok,
            "provider": str(backend_name), "ms": round(elapsed, 1),
            "reason": "" if ok else "inpaint returned an unexpected result",
            "requestedDevice": device,
            "effectiveDevice": effective,
            "fellBack": bool(
                device_from_provider(device) != effective
                and effective != "unknown"),
        })
    except Exception as exc:  # noqa: BLE001
        results["inpaint"].append({
            "name": "lama", "ran": False, "passed": False,
            "provider": "", "ms": None, "reason": str(exc)[:200],
        })

    return results
