use crate::quality::NormalizedImage;
use crate::vision::{
    ExtractionQualityReport, ExtractionRequest, OverlayArtifactSummary, RegionExtractor,
    RegionKind, RegionObservation, RegionSet, RegionSource, RegionStatus, VisionError,
    VisionReadinessReport, validate_mask,
};
use async_trait::async_trait;
use image::{ImageBuffer, Rgb};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::time::{Duration, Instant};
use tempfile::TempDir;

const HELPER_PYTHON_ENV: &str = "CHROMAFRAME_VISION_HELPER_PYTHON";
const MEDIAPIPE_TASK_ENV: &str = "CHROMAFRAME_MEDIAPIPE_TASK_PATH";
const FACE_PARSING_ONNX_ENV: &str = "CHROMAFRAME_FACE_PARSING_ONNX_PATH";
const ASSET_MANIFEST_ENV: &str = "CHROMAFRAME_VISION_ASSET_MANIFEST";
const HELPER_TIMEOUT_ENV: &str = "CHROMAFRAME_VISION_HELPER_TIMEOUT_MS";
const HELPER_SCHEMA_VERSION: u32 = 1;
const FACE_PARSER_MANIFEST_SCHEMA_VERSION: u32 = 1;
const FACE_PARSER_MODEL_ID: &str = "PayamFard123/dermaintel-face-parsing/resnet18.onnx";
const FACE_PARSER_SOURCE_REVISION: &str = "c1c5fd399cc72b45e3613019879fc6c17a7d8f6a";
const FACE_PARSER_EXPECTED_SHA256: &str =
    "0d9bd318e46987c3bdbfacae9e2c0f461cae1c6ac6ea6d43bbe541a91727e33f";
const FACE_PARSER_SOURCE_URL: &str = "https://huggingface.co/PayamFard123/dermaintel-face-parsing/resolve/c1c5fd399cc72b45e3613019879fc6c17a7d8f6a/resnet18.onnx";
const FACE_PARSER_FALLBACK_SOURCE: &str =
    "https://github.com/yakhyo/face-parsing/releases/download/weights/resnet18.onnx";
const FACE_PARSER_LICENSE: &str =
    "MIT model card; weights licensed separately from ChromaFrame code";
const FACE_PARSER_LICENSE_ID: &str = "MIT";
const FACE_PARSER_PROVENANCE: &str =
    "PayamFard123/dermaintel-face-parsing resnet18.onnx; yakhyo/face-parsing weights";
const FACE_PARSER_INPUT_WIDTH: u32 = 512;
const FACE_PARSER_INPUT_HEIGHT: u32 = 512;
const FACE_PARSER_MEAN: [f32; 3] = [0.485, 0.456, 0.406];
const FACE_PARSER_STD: [f32; 3] = [0.229, 0.224, 0.225];
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(20);
const READINESS_OUTPUT_LIMIT: u64 = 128;

const REQUIRED_PACKAGES: &[PythonPackage] = &[
    PythonPackage {
        display_name: "mediapipe",
        import_name: "mediapipe",
    },
    PythonPackage {
        display_name: "opencv-python-headless",
        import_name: "cv2",
    },
    PythonPackage {
        display_name: "numpy",
        import_name: "numpy",
    },
    PythonPackage {
        display_name: "Pillow",
        import_name: "PIL",
    },
];

const ONNX_RUNTIME_PACKAGE: PythonPackage = PythonPackage {
    display_name: "onnxruntime",
    import_name: "onnxruntime",
};

struct PythonPackage {
    display_name: &'static str,
    import_name: &'static str,
}

#[derive(Clone)]
pub struct HelperBackendConfig {
    pub python_path: PathBuf,
    pub mediapipe_task_path: Option<PathBuf>,
    pub face_parsing_onnx_path: Option<PathBuf>,
    pub asset_manifest_path: Option<PathBuf>,
    pub timeout: Duration,
}

impl fmt::Debug for HelperBackendConfig {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("HelperBackendConfig")
            .field("python_path", &redacted_path_state(&self.python_path))
            .field(
                "mediapipe_task_path",
                &redacted_optional_path_state(&self.mediapipe_task_path),
            )
            .field(
                "face_parsing_onnx_path",
                &redacted_optional_path_state(&self.face_parsing_onnx_path),
            )
            .field(
                "asset_manifest_path",
                &redacted_optional_path_state(&self.asset_manifest_path),
            )
            .field("timeout_ms", &self.timeout.as_millis())
            .finish()
    }
}

impl HelperBackendConfig {
    pub fn from_env() -> Self {
        Self {
            python_path: env_path(HELPER_PYTHON_ENV).unwrap_or_else(|| PathBuf::from("python3")),
            mediapipe_task_path: env_path(MEDIAPIPE_TASK_ENV),
            face_parsing_onnx_path: env_path(FACE_PARSING_ONNX_ENV),
            asset_manifest_path: env_path(ASSET_MANIFEST_ENV),
            timeout: env_timeout().unwrap_or(DEFAULT_TIMEOUT),
        }
    }

    pub fn readiness_report(&self) -> VisionReadinessReport {
        let python_version = sanitized_python_version(&self.python_path, self.timeout);
        let missing_packages = missing_python_packages(
            &self.python_path,
            python_version.as_ref(),
            self.timeout,
            true,
        );
        let missing_models = missing_model_assets(self);
        let version_supported = python_version
            .as_deref()
            .is_some_and(is_supported_python_version);
        let backend_available =
            version_supported && missing_models.is_empty() && missing_packages.is_empty();
        VisionReadinessReport {
            backend_available,
            python_version,
            missing_packages,
            missing_models,
            warnings: helper_warnings(backend_available, version_supported),
        }
    }
}

pub struct HelperRegionExtractor {
    config: HelperBackendConfig,
}

impl HelperRegionExtractor {
    pub fn from_env() -> Self {
        Self {
            config: HelperBackendConfig::from_env(),
        }
    }

    pub fn new(config: HelperBackendConfig) -> Self {
        Self { config }
    }

    pub fn readiness_report(&self) -> VisionReadinessReport {
        self.config.readiness_report()
    }
}

impl fmt::Debug for HelperRegionExtractor {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("HelperRegionExtractor")
            .field("config", &self.config)
            .finish()
    }
}

#[async_trait]
impl RegionExtractor for HelperRegionExtractor {
    async fn extract_regions(
        &self,
        image: &NormalizedImage,
        request: &ExtractionRequest,
    ) -> Result<RegionSet, VisionError> {
        let readiness = self.readiness_report();
        if !readiness.backend_available {
            return Err(first_readiness_error(readiness));
        }
        invoke_helper(&self.config, image, request)
    }
}

#[derive(Clone, Serialize, Deserialize)]
struct HelperRequestEnvelope {
    schema_version: u32,
    request_id: String,
    image_path: PathBuf,
    image_width: u32,
    image_height: u32,
    requested_regions: Vec<RegionKind>,
    min_region_pixels: usize,
    erode_pixels: u32,
    max_faces: u32,
    mediapipe_task_path: PathBuf,
    face_parsing_onnx_path: Option<PathBuf>,
    asset_manifest_path: Option<PathBuf>,
    overlay_output_path: Option<PathBuf>,
    overlay_include_points: bool,
    overlay_include_labels: bool,
}

impl fmt::Debug for HelperRequestEnvelope {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("HelperRequestEnvelope")
            .field("schema_version", &self.schema_version)
            .field("request_id", &self.request_id)
            .field("image_path", &redacted_path_state(&self.image_path))
            .field("image_dimensions", &(self.image_width, self.image_height))
            .field("requested_region_count", &self.requested_regions.len())
            .field("min_region_pixels", &self.min_region_pixels)
            .field("erode_pixels", &self.erode_pixels)
            .field("max_faces", &self.max_faces)
            .field(
                "mediapipe_task_path",
                &redacted_path_state(&self.mediapipe_task_path),
            )
            .field(
                "face_parsing_onnx_path",
                &redacted_optional_path_state(&self.face_parsing_onnx_path),
            )
            .field(
                "asset_manifest_path",
                &redacted_optional_path_state(&self.asset_manifest_path),
            )
            .field(
                "overlay_output_path",
                &redacted_optional_path_state(&self.overlay_output_path),
            )
            .field("overlay_include_points", &self.overlay_include_points)
            .field("overlay_include_labels", &self.overlay_include_labels)
            .finish()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct HelperResponseEnvelope {
    schema_version: u32,
    request_id: String,
    status: HelperStatus,
    region_set: Option<RegionSet>,
    overlay: Option<OverlayArtifactSummary>,
    error_code: Option<HelperErrorCode>,
    warnings: Vec<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum HelperStatus {
    Ok,
    Error,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum HelperErrorCode {
    NoFace,
    MultipleFaces,
    ModelMissing,
    PackageMissing,
    BackendVersionMismatch,
    ExtractionFailed,
    InvalidRequest,
}

struct HelperProcessResult {
    exited_successfully: bool,
    diagnostics: RedactedProcessDiagnostics,
}

struct RedactedProcessDiagnostics {
    stdout_bytes: usize,
    stderr_bytes: usize,
}

impl RedactedProcessDiagnostics {
    fn empty() -> Self {
        Self {
            stdout_bytes: 0,
            stderr_bytes: 0,
        }
    }

    fn public_code(&self) -> &'static str {
        match (self.stdout_bytes > 0, self.stderr_bytes > 0) {
            (true, true) => "stdout_stderr_redacted",
            (true, false) => "stdout_redacted",
            (false, true) => "stderr_redacted",
            (false, false) => "no_response",
        }
    }
}

pub fn unavailable_region_set(width: u32, height: u32, reason: impl Into<String>) -> RegionSet {
    let reason = sanitize_reason(reason.into());
    RegionSet {
        image_width: width,
        image_height: height,
        regions: Vec::new(),
        extraction_quality: ExtractionQualityReport {
            faces_detected: 0,
            selected_face_index: None,
            backend: "local_helper".to_string(),
            warnings: vec![reason.clone()],
        },
        warnings: vec![reason],
    }
}

fn invoke_helper(
    config: &HelperBackendConfig,
    image: &NormalizedImage,
    request: &ExtractionRequest,
) -> Result<RegionSet, VisionError> {
    let temp_dir = tempfile::Builder::new()
        .prefix("chromaframe-vision-")
        .tempdir()
        .map_err(|_| VisionError::HelperIo)?;
    let request_id = helper_request_id(image, request);
    let image_path = temp_dir.path().join("input.png");
    let request_path = temp_dir.path().join("request.json");
    let response_path = temp_dir.path().join("response.json");
    write_normalized_image(&image_path, image)?;
    write_helper_request(
        config,
        image,
        request,
        &request_id,
        &image_path,
        &request_path,
    )?;
    let process = run_helper_process(config, &request_path, &response_path)?;
    let response = match read_helper_response(&response_path) {
        Ok(response) => response,
        Err(error) if process.exited_successfully => return Err(error),
        Err(_) => {
            return Err(VisionError::HelperProtocolError(format!(
                "helper_process_failed_{}",
                process.diagnostics.public_code()
            )));
        }
    };
    if !process.exited_successfully && response.status == HelperStatus::Ok {
        return Err(VisionError::HelperProtocolError(format!(
            "helper_process_failed_{}",
            process.diagnostics.public_code()
        )));
    }
    let region_set =
        validate_helper_response(response, &request_id, image.width, image.height, request)?;
    cleanup_temp_dir(temp_dir, request.keep_debug_artifacts)?;
    Ok(region_set)
}

fn write_helper_request(
    config: &HelperBackendConfig,
    image: &NormalizedImage,
    request: &ExtractionRequest,
    request_id: &str,
    image_path: &Path,
    request_path: &Path,
) -> Result<(), VisionError> {
    let validated_overlay_output_path = validate_overlay_output_policy(request)?;
    let Some(mediapipe_task_path) = &config.mediapipe_task_path else {
        return Err(VisionError::ModelMissing(
            "mediapipe_face_landmarker_task".to_string(),
        ));
    };
    let envelope = HelperRequestEnvelope {
        schema_version: HELPER_SCHEMA_VERSION,
        request_id: request_id.to_string(),
        image_path: image_path.to_path_buf(),
        image_width: image.width,
        image_height: image.height,
        requested_regions: request.requested_regions.clone(),
        min_region_pixels: request.min_region_pixels,
        erode_pixels: request.erode_pixels,
        max_faces: request.max_faces,
        mediapipe_task_path: mediapipe_task_path.clone(),
        face_parsing_onnx_path: config.face_parsing_onnx_path.clone(),
        asset_manifest_path: config.asset_manifest_path.clone(),
        overlay_output_path: validated_overlay_output_path,
        overlay_include_points: request
            .overlay_request
            .as_ref()
            .is_some_and(|overlay| overlay.include_points),
        overlay_include_labels: request
            .overlay_request
            .as_ref()
            .is_some_and(|overlay| overlay.include_labels),
    };
    let json = serde_json::to_vec(&envelope)
        .map_err(|_| VisionError::HelperProtocolError("request_json_encode_failed".to_string()))?;
    fs::write(request_path, json).map_err(|_| VisionError::HelperIo)
}

fn validate_overlay_output_policy(
    request: &ExtractionRequest,
) -> Result<Option<PathBuf>, VisionError> {
    let Some(overlay) = &request.overlay_request else {
        return Ok(None);
    };
    if !overlay.output_path.is_absolute() {
        return Err(VisionError::HelperProtocolError(
            "overlay_output_must_be_absolute".to_string(),
        ));
    }
    let Ok(repo_root) =
        workspace_root().and_then(|root| root.canonicalize().map_err(|_| VisionError::HelperIo))
    else {
        return Ok(Some(overlay.output_path.clone()));
    };
    if !request.keep_debug_artifacts {
        validate_overlay_target_outside_repo(&overlay.output_path, &repo_root)?;
        validate_overlay_target_outside_repo(
            &overlay_sidecar_path(&overlay.output_path),
            &repo_root,
        )?;
    }
    Ok(Some(overlay.output_path.clone()))
}

fn overlay_sidecar_path(path: &Path) -> PathBuf {
    let extension = path
        .extension()
        .and_then(|extension| extension.to_str())
        .map(|extension| format!("{extension}.json"))
        .unwrap_or_else(|| "json".to_string());
    path.with_extension(extension)
}

fn validate_overlay_target_outside_repo(
    target: &Path,
    repo_root: &Path,
) -> Result<(), VisionError> {
    if target.exists() {
        let metadata = fs::symlink_metadata(target).map_err(|_| VisionError::HelperIo)?;
        if metadata.file_type().is_symlink() {
            return Err(VisionError::HelperProtocolError(
                "overlay_existing_symlink_requires_debug_override".to_string(),
            ));
        }
        let resolved_target = target.canonicalize().map_err(|_| VisionError::HelperIo)?;
        if resolved_target.starts_with(repo_root) {
            return Err(VisionError::HelperProtocolError(
                "overlay_output_inside_repo_requires_debug_override".to_string(),
            ));
        }
        return Ok(());
    }
    let resolved_parent = resolve_overlay_parent(target)?;
    if resolved_parent.starts_with(repo_root) {
        return Err(VisionError::HelperProtocolError(
            "overlay_output_inside_repo_requires_debug_override".to_string(),
        ));
    }
    Ok(())
}

fn workspace_root() -> Result<PathBuf, VisionError> {
    let mut current = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    loop {
        if current.join(".git").exists() || is_workspace_manifest(&current.join("Cargo.toml")) {
            return Ok(current);
        }
        if !current.pop() {
            return PathBuf::from(env!("CARGO_MANIFEST_DIR"))
                .canonicalize()
                .map_err(|_| VisionError::HelperIo);
        }
    }
}

fn is_workspace_manifest(path: &Path) -> bool {
    fs::read_to_string(path)
        .map(|contents| contents.lines().any(|line| line.trim() == "[workspace]"))
        .unwrap_or(false)
}

fn resolve_overlay_parent(candidate: &Path) -> Result<PathBuf, VisionError> {
    let parent = candidate.parent().unwrap_or_else(|| Path::new("."));
    let Some(existing) = nearest_existing_ancestor(parent) else {
        return Err(VisionError::HelperProtocolError(
            "overlay_parent_missing".to_string(),
        ));
    };
    existing.canonicalize().map_err(|_| VisionError::HelperIo)
}

fn nearest_existing_ancestor(path: &Path) -> Option<PathBuf> {
    let mut current = path;
    loop {
        if current.exists() {
            return Some(current.to_path_buf());
        }
        current = current.parent()?;
    }
}

fn run_helper_process(
    config: &HelperBackendConfig,
    request_path: &Path,
    response_path: &Path,
) -> Result<HelperProcessResult, VisionError> {
    let mut child = Command::new(&config.python_path)
        .arg("-m")
        .arg("chromaframe_vision_helper")
        .arg("extract")
        .arg("--request-json")
        .arg(request_path)
        .arg("--response-json")
        .arg(response_path)
        .current_dir(helper_package_root())
        .env("PYTHONPATH", helper_python_path())
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
        .map_err(|_| VisionError::BackendUnavailable)?;
    let started_at = Instant::now();
    loop {
        if let Some(status) = child.try_wait().map_err(|_| VisionError::HelperIo)? {
            return Ok(HelperProcessResult {
                exited_successfully: status.success(),
                diagnostics: RedactedProcessDiagnostics::empty(),
            });
        }
        if started_at.elapsed() >= config.timeout {
            let _ = child.kill();
            let _ = child.wait();
            return Err(VisionError::HelperTimeout);
        }
        std::thread::sleep(Duration::from_millis(20));
    }
}

fn read_helper_response(response_path: &Path) -> Result<HelperResponseEnvelope, VisionError> {
    let bytes = fs::read(response_path)
        .map_err(|_| VisionError::HelperProtocolError("response_missing".to_string()))?;
    serde_json::from_slice(&bytes)
        .map_err(|_| VisionError::HelperProtocolError("response_json_invalid".to_string()))
}

fn validate_helper_response(
    response: HelperResponseEnvelope,
    request_id: &str,
    width: u32,
    height: u32,
    request: &ExtractionRequest,
) -> Result<RegionSet, VisionError> {
    if response.schema_version != HELPER_SCHEMA_VERSION {
        return Err(VisionError::HelperProtocolError(
            "schema_version_mismatch".to_string(),
        ));
    }
    if response.request_id != request_id {
        return Err(VisionError::HelperProtocolError(
            "request_id_mismatch".to_string(),
        ));
    }
    validate_response_status_consistency(&response)?;
    if response.status != HelperStatus::Ok {
        return Err(helper_error_to_vision_error(response.error_code));
    }
    let Some(region_set) = response.region_set else {
        return Err(VisionError::HelperProtocolError(
            "region_set_missing".to_string(),
        ));
    };
    validate_region_set_from_helper(&region_set, width, height, request)?;
    validate_overlay_response(&response.overlay, request)?;
    region_set.parse()
}

fn validate_response_status_consistency(
    response: &HelperResponseEnvelope,
) -> Result<(), VisionError> {
    match (
        response.status,
        response.error_code,
        response.region_set.is_some(),
    ) {
        (HelperStatus::Ok, None, true) => Ok(()),
        (HelperStatus::Error, Some(_), false) => Ok(()),
        (HelperStatus::Ok, Some(_), _) => Err(VisionError::HelperProtocolError(
            "ok_response_has_error_code".to_string(),
        )),
        (HelperStatus::Error, None, _) => Err(VisionError::HelperProtocolError(
            "error_response_missing_error_code".to_string(),
        )),
        (HelperStatus::Error, Some(_), true) => Err(VisionError::HelperProtocolError(
            "error_response_has_region_set".to_string(),
        )),
        (HelperStatus::Ok, None, false) => Err(VisionError::HelperProtocolError(
            "ok_response_missing_region_set".to_string(),
        )),
    }
}

fn validate_region_set_from_helper(
    region_set: &RegionSet,
    width: u32,
    height: u32,
    request: &ExtractionRequest,
) -> Result<(), VisionError> {
    if region_set.image_width != width || region_set.image_height != height {
        return Err(VisionError::HelperProtocolError(
            "dimension_mismatch".to_string(),
        ));
    }
    if region_set.extraction_quality.faces_detected == 0 {
        return Err(VisionError::HelperProtocolError(
            "face_count_missing".to_string(),
        ));
    }
    if region_set.extraction_quality.faces_detected > request.max_faces {
        return Err(helper_error_to_vision_error(Some(
            HelperErrorCode::MultipleFaces,
        )));
    }
    let Some(selected_face_index) = region_set.extraction_quality.selected_face_index else {
        return Err(VisionError::HelperProtocolError(
            "selected_face_index_missing".to_string(),
        ));
    };
    if selected_face_index >= region_set.extraction_quality.faces_detected {
        return Err(VisionError::HelperProtocolError(
            "selected_face_index_out_of_range".to_string(),
        ));
    }
    for region in &region_set.regions {
        validate_observation_from_helper(region, width, height, request.min_region_pixels)?;
    }
    for required in required_mvp_regions(request) {
        if !has_measured_region(region_set, required) {
            return Err(VisionError::HelperProtocolError(format!(
                "mvp_region_missing_{required:?}"
            )));
        }
    }
    validate_requested_mvp_optional_regions(region_set, request)?;
    Ok(())
}

fn validate_requested_mvp_optional_regions(
    region_set: &RegionSet,
    request: &ExtractionRequest,
) -> Result<(), VisionError> {
    if request.requested_regions.contains(&RegionKind::Hair) {
        let mut hair_observations = region_set
            .regions
            .iter()
            .filter(|region| region.kind == RegionKind::Hair);
        let Some(hair) = hair_observations.next() else {
            return Err(VisionError::HelperProtocolError(
                "mvp_region_missing_Hair".to_string(),
            ));
        };
        if hair_observations.next().is_some() {
            return Err(VisionError::HelperProtocolError(
                "semantic_parser_hair_duplicate".to_string(),
            ));
        }
        validate_requested_hair_observation(hair)?;
    }
    if request.requested_regions.contains(&RegionKind::Beard)
        && !has_region_observation(region_set, RegionKind::Beard)
    {
        return Err(VisionError::HelperProtocolError(
            "mvp_region_missing_Beard".to_string(),
        ));
    }
    Ok(())
}

fn validate_requested_hair_observation(region: &RegionObservation) -> Result<(), VisionError> {
    const EXPECTED_LOW_EVIDENCE_REASON: &str = "semantic_parser_hair_evidence_low";

    if matches!(region.status, RegionStatus::Measured)
        && region.source == RegionSource::ParserMask
        && region.not_measured_reason.is_none()
    {
        return Ok(());
    }

    if let RegionStatus::NotMeasured { reason } = &region.status {
        let duplicated_reason = region.not_measured_reason.as_deref();
        if reason == EXPECTED_LOW_EVIDENCE_REASON
            && duplicated_reason == Some(EXPECTED_LOW_EVIDENCE_REASON)
        {
            return Ok(());
        }
    }

    Err(VisionError::HelperProtocolError(
        "semantic_parser_hair_response_invalid".to_string(),
    ))
}

fn validate_observation_from_helper(
    region: &RegionObservation,
    width: u32,
    height: u32,
    min_region_pixels: usize,
) -> Result<(), VisionError> {
    match (&region.status, region.source, &region.mask) {
        (
            RegionStatus::Measured,
            RegionSource::MediapipeLandmarks | RegionSource::ParserMask,
            Some(mask),
        ) => {
            validate_mask(mask, width, height)?;
            validate_region_area(region, mask, min_region_pixels)
        }
        (RegionStatus::Approximate, RegionSource::Approximation, Some(mask)) => {
            if region
                .approximate_reason
                .as_deref()
                .unwrap_or_default()
                .is_empty()
            {
                return Err(VisionError::HelperProtocolError(
                    "approximate_reason_missing".to_string(),
                ));
            }
            validate_mask(mask, width, height)?;
            validate_region_area(region, mask, min_region_pixels)
        }
        (RegionStatus::LowEvidence, RegionSource::Approximation, Some(mask)) => {
            let reason = region
                .approximate_reason
                .as_deref()
                .or(region.not_measured_reason.as_deref())
                .unwrap_or_default();
            if reason.is_empty() {
                return Err(VisionError::HelperProtocolError(
                    "low_evidence_reason_missing".to_string(),
                ));
            }
            validate_mask(mask, width, height)?;
            validate_region_area(region, mask, min_region_pixels)
        }
        (RegionStatus::NotMeasured { reason }, RegionSource::NotMeasured, None) => {
            if reason.is_empty() {
                return Err(VisionError::HelperProtocolError(
                    "not_measured_reason_missing".to_string(),
                ));
            }
            Ok(())
        }
        _ => Err(VisionError::HelperProtocolError(
            "region_status_source_mismatch".to_string(),
        )),
    }
}

fn validate_region_area(
    region: &RegionObservation,
    mask: &crate::vision::RegionMask,
    min_region_pixels: usize,
) -> Result<(), VisionError> {
    let area = mask_area_estimate(mask);
    if area >= min_region_pixels as f32 {
        return Ok(());
    }
    Err(VisionError::HelperProtocolError(format!(
        "region_area_too_small_{:?}",
        region.kind
    )))
}

fn mask_area_estimate(mask: &crate::vision::RegionMask) -> f32 {
    match mask {
        crate::vision::RegionMask::Polygon { polygons } => {
            polygons.iter().map(|polygon| polygon_area(polygon)).sum()
        }
        crate::vision::RegionMask::RleBitmap { counts, .. } => counts
            .iter()
            .enumerate()
            .filter(|(index, _)| index % 2 == 1)
            .map(|(_, count)| *count as f32)
            .sum(),
    }
}

fn polygon_area(polygon: &[crate::vision::Point]) -> f32 {
    if polygon.len() < 3 {
        return 0.0;
    }
    let mut area = 0.0;
    let mut previous = polygon.len() - 1;
    for current in 0..polygon.len() {
        area += polygon[previous].x * polygon[current].y - polygon[current].x * polygon[previous].y;
        previous = current;
    }
    area.abs() * 0.5
}

fn validate_overlay_response(
    overlay: &Option<OverlayArtifactSummary>,
    request: &ExtractionRequest,
) -> Result<(), VisionError> {
    if request.overlay_request.is_none() && overlay.is_some() {
        return Err(VisionError::HelperProtocolError(
            "unexpected_overlay_summary".to_string(),
        ));
    }
    if request.overlay_request.is_some() && overlay.is_none() {
        return Err(VisionError::HelperProtocolError(
            "overlay_summary_missing".to_string(),
        ));
    }
    Ok(())
}

fn required_mvp_regions(request: &ExtractionRequest) -> impl Iterator<Item = RegionKind> + '_ {
    [
        RegionKind::Skin,
        RegionKind::Brow,
        RegionKind::Iris,
        RegionKind::Sclera,
        RegionKind::Lip,
    ]
    .into_iter()
    .filter(|kind| request.requested_regions.contains(kind))
}

fn has_measured_region(region_set: &RegionSet, kind: RegionKind) -> bool {
    region_set
        .regions
        .iter()
        .any(|region| region.kind == kind && matches!(region.status, RegionStatus::Measured))
}

fn has_region_observation(region_set: &RegionSet, kind: RegionKind) -> bool {
    region_set.regions.iter().any(|region| region.kind == kind)
}

fn helper_error_to_vision_error(error_code: Option<HelperErrorCode>) -> VisionError {
    match error_code.unwrap_or(HelperErrorCode::ExtractionFailed) {
        HelperErrorCode::ModelMissing => {
            VisionError::ModelMissing("mediapipe_face_landmarker_task".to_string())
        }
        HelperErrorCode::PackageMissing => {
            VisionError::PackageMissing("python_dependency".to_string())
        }
        HelperErrorCode::BackendVersionMismatch => VisionError::BackendVersionMismatch,
        HelperErrorCode::NoFace => VisionError::HelperProtocolError("no_face_detected".to_string()),
        HelperErrorCode::MultipleFaces => {
            VisionError::HelperProtocolError("multiple_faces_detected".to_string())
        }
        HelperErrorCode::InvalidRequest => {
            VisionError::HelperProtocolError("invalid_helper_request".to_string())
        }
        HelperErrorCode::ExtractionFailed => {
            VisionError::HelperProtocolError("extraction_failed".to_string())
        }
    }
}

fn write_normalized_image(path: &Path, image: &NormalizedImage) -> Result<(), VisionError> {
    let mut output = ImageBuffer::<Rgb<u8>, Vec<u8>>::new(image.width, image.height);
    for (index, pixel) in image.pixels.iter().enumerate() {
        let x = (index as u32) % image.width;
        let y = (index as u32) / image.width;
        output.put_pixel(x, y, Rgb([to_u8(pixel.r), to_u8(pixel.g), to_u8(pixel.b)]));
    }
    output.save(path).map_err(|_| VisionError::HelperIo)
}

fn to_u8(value: f32) -> u8 {
    (value.clamp(0.0, 1.0) * 255.0).round() as u8
}

fn cleanup_temp_dir(temp_dir: TempDir, keep_debug_artifacts: bool) -> Result<(), VisionError> {
    if keep_debug_artifacts {
        let _ = temp_dir.keep();
        return Ok(());
    }
    temp_dir.close().map_err(|_| VisionError::HelperIo)
}

fn helper_request_id(image: &NormalizedImage, request: &ExtractionRequest) -> String {
    format!(
        "vision-v{HELPER_SCHEMA_VERSION}-{}x{}-{}",
        image.width,
        image.height,
        request.requested_regions.len()
    )
}

fn helper_package_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("vision-helper")
}

fn helper_python_path() -> OsString {
    helper_package_root().into_os_string()
}

fn env_path(name: &str) -> Option<PathBuf> {
    let value = env::var_os(name)?;
    if value.is_empty() {
        return None;
    }
    Some(PathBuf::from(value))
}

fn env_timeout() -> Option<Duration> {
    let millis = env::var(HELPER_TIMEOUT_ENV).ok()?.parse::<u64>().ok()?;
    Some(Duration::from_millis(millis.clamp(1_000, 120_000)))
}

struct BoundedCommandResult {
    status: Option<ExitStatus>,
    stdout: Vec<u8>,
    stderr: Vec<u8>,
    timed_out: bool,
}

fn sanitized_python_version(python_path: &Path, timeout: Duration) -> Option<String> {
    let output = run_bounded_command(python_path, &["--version"], timeout, true).ok()?;
    if output.timed_out || !output.status.is_some_and(|status| status.success()) {
        return None;
    }
    let stdout = String::from_utf8_lossy(&output.stdout);
    if let Some(version) = parse_canonical_python_version(stdout.trim()) {
        return Some(version);
    }
    let stderr = String::from_utf8_lossy(&output.stderr);
    parse_canonical_python_version(stderr.trim())
}

fn parse_canonical_python_version(output: &str) -> Option<String> {
    let trimmed = output.trim();
    let mut parts = trimmed.split_whitespace();
    let prefix = parts.next()?;
    let version = parts.next()?;
    if parts.next().is_some() || prefix != "Python" {
        return None;
    }
    let mut numbers = version.split('.');
    let major = numbers.next()?;
    let minor = numbers.next()?;
    let patch = numbers.next()?;
    if numbers.next().is_some() || major != "3" || !matches!(minor, "11" | "12") {
        return None;
    }
    if !patch.chars().all(|character| character.is_ascii_digit()) {
        return None;
    }
    Some(format!("Python {major}.{minor}.{patch}"))
}

fn is_supported_python_version(version: &str) -> bool {
    version.starts_with("Python 3.11.") || version.starts_with("Python 3.12.")
}

fn missing_python_packages(
    python_path: &Path,
    python_version: Option<&String>,
    timeout: Duration,
    parser_configured: bool,
) -> Vec<String> {
    let required_packages = required_python_packages(parser_configured);
    if python_version.is_none() {
        return required_packages
            .iter()
            .map(|package| package.display_name.to_string())
            .collect();
    }
    required_packages
        .iter()
        .filter(|package| !python_can_import(python_path, package.import_name, timeout))
        .map(|package| package.display_name.to_string())
        .collect()
}

fn required_python_packages(_parser_configured: bool) -> Vec<&'static PythonPackage> {
    let mut packages: Vec<&'static PythonPackage> = REQUIRED_PACKAGES.iter().collect();
    packages.push(&ONNX_RUNTIME_PACKAGE);
    packages
}

fn python_can_import(python_path: &Path, import_name: &str, timeout: Duration) -> bool {
    if !is_safe_import_name(import_name) {
        return false;
    }
    run_bounded_command(
        python_path,
        &["-c", &format!("import {import_name}")],
        timeout,
        false,
    )
    .map(|output| !output.timed_out && output.status.is_some_and(|status| status.success()))
    .unwrap_or(false)
}

fn run_bounded_command(
    program: &Path,
    args: &[&str],
    timeout: Duration,
    capture_stdout: bool,
) -> Result<BoundedCommandResult, VisionError> {
    let stdout_capture = if capture_stdout {
        Some(
            tempfile::Builder::new()
                .prefix("chromaframe-readiness-")
                .tempfile()
                .map_err(|_| VisionError::HelperIo)?,
        )
    } else {
        None
    };
    let stdout = match &stdout_capture {
        Some(file) => Stdio::from(
            file.as_file()
                .try_clone()
                .map_err(|_| VisionError::HelperIo)?,
        ),
        None => Stdio::null(),
    };
    let stderr_capture = if capture_stdout {
        Some(
            tempfile::Builder::new()
                .prefix("chromaframe-readiness-")
                .tempfile()
                .map_err(|_| VisionError::HelperIo)?,
        )
    } else {
        None
    };
    let stderr = match &stderr_capture {
        Some(file) => Stdio::from(
            file.as_file()
                .try_clone()
                .map_err(|_| VisionError::HelperIo)?,
        ),
        None => Stdio::null(),
    };
    let mut child = Command::new(program)
        .args(args)
        .stdin(Stdio::null())
        .stdout(stdout)
        .stderr(stderr)
        .spawn()
        .map_err(|_| VisionError::BackendUnavailable)?;
    let started_at = Instant::now();
    loop {
        if let Some(status) = child.try_wait().map_err(|_| VisionError::HelperIo)? {
            let stdout = read_captured_output(stdout_capture.as_ref())?;
            let stderr = read_captured_output(stderr_capture.as_ref())?;
            return Ok(BoundedCommandResult {
                status: Some(status),
                stdout,
                stderr,
                timed_out: false,
            });
        }
        if started_at.elapsed() >= timeout {
            let _ = child.kill();
            let _ = child.wait();
            return Ok(BoundedCommandResult {
                status: None,
                stdout: Vec::new(),
                stderr: Vec::new(),
                timed_out: true,
            });
        }
        std::thread::sleep(Duration::from_millis(20));
    }
}

fn read_captured_output(file: Option<&tempfile::NamedTempFile>) -> Result<Vec<u8>, VisionError> {
    let Some(file) = file else {
        return Ok(Vec::new());
    };
    let mut output = Vec::new();
    let mut reader = fs::File::open(file.path()).map_err(|_| VisionError::HelperIo)?;
    reader
        .by_ref()
        .take(READINESS_OUTPUT_LIMIT)
        .read_to_end(&mut output)
        .map_err(|_| VisionError::HelperIo)?;
    Ok(output)
}

fn is_safe_import_name(import_name: &str) -> bool {
    !import_name.is_empty()
        && import_name
            .chars()
            .all(|character| character.is_ascii_alphanumeric() || character == '_')
}

fn missing_model_assets(config: &HelperBackendConfig) -> Vec<String> {
    let mut missing = Vec::new();
    push_missing_path(
        &mut missing,
        "mediapipe_face_landmarker_task",
        config.mediapipe_task_path.as_deref(),
    );
    push_missing_path(
        &mut missing,
        "face_parsing_onnx",
        config.face_parsing_onnx_path.as_deref(),
    );
    if let Some(path) = &config.face_parsing_onnx_path {
        validate_optional_parser_manifest(
            &mut missing,
            path,
            config.asset_manifest_path.as_deref(),
        );
    } else {
        missing.push("face_parsing_manifest_not_configured".to_string());
    }
    missing
}

#[derive(Deserialize)]
struct ParserAssetManifest {
    face_parsing_onnx: ParserAssetEntry,
}

#[derive(Deserialize)]
struct ParserAssetEntry {
    manifest_schema_version: u32,
    model_id: String,
    sha256: String,
    expected_sha256: String,
    license: String,
    #[serde(default)]
    license_id: String,
    approved_for_use: bool,
    model_type: String,
    source_url: String,
    source_revision: String,
    fallback_source: String,
    provenance: String,
    labels: BTreeMap<String, u32>,
    input: ParserInputSpec,
    normalization: ParserNormalizationSpec,
    output: ParserOutputSpec,
}

#[derive(Deserialize)]
struct ParserInputSpec {
    width: u32,
    height: u32,
}

#[derive(Deserialize)]
struct ParserNormalizationSpec {
    mean: [f32; 3],
    std: [f32; 3],
}

#[derive(Deserialize)]
struct ParserOutputSpec {
    #[serde(rename = "type")]
    output_type: String,
}

fn validate_optional_parser_manifest(
    missing: &mut Vec<String>,
    parser_path: &Path,
    manifest_path: Option<&Path>,
) {
    let Some(manifest_path) = manifest_path else {
        missing.push("face_parsing_manifest_not_configured".to_string());
        return;
    };
    if !manifest_path.is_file() {
        missing.push("face_parsing_manifest_not_readable".to_string());
        return;
    }
    let Ok(bytes) = fs::read(manifest_path) else {
        missing.push("face_parsing_manifest_not_readable".to_string());
        return;
    };
    let Ok(manifest) = serde_json::from_slice::<ParserAssetManifest>(&bytes) else {
        missing.push("face_parsing_manifest_invalid".to_string());
        return;
    };
    if let Some(reason) = parser_manifest_rejection_reason(&manifest.face_parsing_onnx) {
        missing.push(reason.to_string());
        return;
    }
    let Ok(parser_bytes) = fs::read(parser_path) else {
        missing.push("face_parsing_onnx_not_readable".to_string());
        return;
    };
    let digest = Sha256::digest(parser_bytes);
    let actual = encode_lower_hex(digest.as_ref());
    if actual != manifest.face_parsing_onnx.sha256.to_ascii_lowercase() {
        missing.push("face_parsing_sha256_mismatch".to_string());
    }
}

fn parser_manifest_rejection_reason(entry: &ParserAssetEntry) -> Option<&'static str> {
    const REJECTED_LABELS: &[&str] = &["beard", "moustache", "mustache", "stubble", "facial_hair"];

    if !entry.approved_for_use || entry.model_type != "baseline_face_parser" {
        return Some("face_parsing_license_not_approved");
    }
    if entry.manifest_schema_version != FACE_PARSER_MANIFEST_SCHEMA_VERSION
        || entry.model_id != FACE_PARSER_MODEL_ID
        || entry.source_revision != FACE_PARSER_SOURCE_REVISION
    {
        return Some("face_parsing_license_not_approved");
    }
    if entry.expected_sha256 != FACE_PARSER_EXPECTED_SHA256
        || entry.sha256 != FACE_PARSER_EXPECTED_SHA256
    {
        return Some("face_parsing_license_not_approved");
    }
    if entry.source_url != FACE_PARSER_SOURCE_URL
        || entry.fallback_source != FACE_PARSER_FALLBACK_SOURCE
        || entry.license != FACE_PARSER_LICENSE
        || entry.license_id != FACE_PARSER_LICENSE_ID
        || entry.provenance != FACE_PARSER_PROVENANCE
    {
        return Some("face_parsing_license_not_approved");
    }
    if entry.sha256.len() != 64
        || !entry
            .sha256
            .chars()
            .all(|character| character.is_ascii_hexdigit())
    {
        return Some("face_parsing_license_not_approved");
    }
    if entry.input.width != FACE_PARSER_INPUT_WIDTH
        || entry.input.height != FACE_PARSER_INPUT_HEIGHT
    {
        return Some("face_parsing_license_not_approved");
    }
    if !float_triplet_matches(entry.normalization.mean, FACE_PARSER_MEAN)
        || !float_triplet_matches(entry.normalization.std, FACE_PARSER_STD)
    {
        return Some("face_parsing_license_not_approved");
    }
    if entry.output.output_type.as_str() != "logits_argmax" {
        return Some("face_parsing_license_not_approved");
    }
    if entry.labels.is_empty() {
        return Some("face_parsing_labels_invalid");
    }
    if entry
        .labels
        .keys()
        .any(|label| REJECTED_LABELS.contains(&label.as_str()))
    {
        return Some("face_parsing_facial_hair_labels_rejected");
    }
    if !labels_match_expected_contract(&entry.labels) {
        return Some("face_parsing_labels_invalid");
    }
    None
}

fn float_triplet_matches(actual: [f32; 3], expected: [f32; 3]) -> bool {
    actual
        .iter()
        .zip(expected.iter())
        .all(|(left, right)| left.is_finite() && (*left - *right).abs() <= 0.000_001)
}

fn labels_match_expected_contract(labels: &BTreeMap<String, u32>) -> bool {
    const EXPECTED_LABELS: &[(&str, u32)] = &[
        ("background", 0),
        ("skin", 1),
        ("l_brow", 2),
        ("r_brow", 3),
        ("l_eye", 4),
        ("r_eye", 5),
        ("eye_g", 6),
        ("l_ear", 7),
        ("r_ear", 8),
        ("ear_r", 9),
        ("nose", 10),
        ("mouth", 11),
        ("u_lip", 12),
        ("l_lip", 13),
        ("neck", 14),
        ("neck_l", 15),
        ("cloth", 16),
        ("hair", 17),
        ("hat", 18),
    ];
    labels.len() == EXPECTED_LABELS.len()
        && EXPECTED_LABELS
            .iter()
            .all(|(label, id)| labels.get(*label) == Some(id))
}

fn encode_lower_hex(bytes: &[u8]) -> String {
    const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";

    let mut encoded = String::with_capacity(bytes.len() * 2);
    for byte in bytes {
        encoded.push(HEX_DIGITS[(byte >> 4) as usize] as char);
        encoded.push(HEX_DIGITS[(byte & 0x0f) as usize] as char);
    }
    encoded
}

fn push_missing_path(missing: &mut Vec<String>, label: &str, path: Option<&Path>) {
    let Some(path) = path else {
        missing.push(format!("{label}_not_configured"));
        return;
    };
    if !path.is_file() {
        missing.push(format!("{label}_not_readable"));
    }
}

fn helper_warnings(backend_available: bool, version_supported: bool) -> Vec<String> {
    let mut warnings = Vec::new();
    if !version_supported {
        warnings.push("python_version_target_3_11_or_3_12".to_string());
    }
    if !backend_available {
        warnings.push("local_helper_not_ready".to_string());
    }
    warnings
}

fn first_readiness_error(readiness: VisionReadinessReport) -> VisionError {
    if readiness
        .python_version
        .as_deref()
        .is_some_and(|version| !is_supported_python_version(version))
    {
        return VisionError::BackendVersionMismatch;
    }
    if let Some(package) = readiness.missing_packages.first() {
        return VisionError::PackageMissing(package.clone());
    }
    if let Some(model) = readiness.missing_models.first() {
        if model.contains("sha256_mismatch") {
            return VisionError::ModelChecksumMismatch;
        }
        if model.contains("license_not_approved") {
            return VisionError::UnsupportedModelLicense;
        }
        return VisionError::ModelMissing(model.clone());
    }
    VisionError::BackendUnavailable
}

fn sanitize_reason(value: String) -> String {
    let safe: String = value
        .chars()
        .filter(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.' | ' '))
        .take(96)
        .collect();
    if safe.trim().is_empty() {
        "unspecified".to_string()
    } else {
        safe
    }
}

fn redacted_path_state(path: &Path) -> &'static str {
    if path.as_os_str().is_empty() {
        "not_configured"
    } else {
        "configured_redacted"
    }
}

fn redacted_optional_path_state(path: &Option<PathBuf>) -> &'static str {
    if path.is_some() {
        "configured_redacted"
    } else {
        "not_configured"
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::vision::{Point, RegionMask};

    #[test]
    fn rejects_mismatched_helper_request_id() {
        let response = HelperResponseEnvelope {
            schema_version: HELPER_SCHEMA_VERSION,
            request_id: "wrong".to_string(),
            status: HelperStatus::Ok,
            region_set: None,
            overlay: None,
            error_code: None,
            warnings: Vec::new(),
        };
        assert!(matches!(
            validate_helper_response(response, "expected", 4, 4, &ExtractionRequest::default()),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[test]
    fn validates_mvp_region_presence() {
        let square = RegionMask::Polygon {
            polygons: vec![vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 9.0, y: 0.0 },
                Point { x: 9.0, y: 9.0 },
                Point { x: 0.0, y: 9.0 },
            ]],
        };
        let region = |kind| RegionObservation {
            kind,
            status: RegionStatus::Measured,
            source: RegionSource::MediapipeLandmarks,
            confidence: 0.9,
            mask: Some(square.clone()),
            sample_hint: Some(20),
            approximate_reason: None,
            not_measured_reason: None,
        };
        let region_set = RegionSet {
            image_width: 10,
            image_height: 10,
            regions: vec![
                region(RegionKind::Skin),
                region(RegionKind::Brow),
                region(RegionKind::Iris),
                region(RegionKind::Sclera),
                region(RegionKind::Lip),
            ],
            extraction_quality: ExtractionQualityReport {
                faces_detected: 1,
                selected_face_index: Some(0),
                backend: "mediapipe".to_string(),
                warnings: Vec::new(),
            },
            warnings: Vec::new(),
        };
        let response = HelperResponseEnvelope {
            schema_version: HELPER_SCHEMA_VERSION,
            request_id: "ok".to_string(),
            status: HelperStatus::Ok,
            region_set: Some(region_set),
            overlay: None,
            error_code: None,
            warnings: Vec::new(),
        };
        assert!(
            validate_helper_response(
                response,
                "ok",
                10,
                10,
                &ExtractionRequest {
                    requested_regions: vec![
                        RegionKind::Skin,
                        RegionKind::Brow,
                        RegionKind::Iris,
                        RegionKind::Sclera,
                        RegionKind::Lip,
                    ],
                    ..ExtractionRequest::default()
                }
            )
            .is_ok()
        );
    }

    #[test]
    fn protocol_error_codes_are_preserved() {
        for (code, expected) in [
            (HelperErrorCode::NoFace, "no_face_detected"),
            (HelperErrorCode::MultipleFaces, "multiple_faces_detected"),
            (HelperErrorCode::InvalidRequest, "invalid_helper_request"),
        ] {
            let response = HelperResponseEnvelope {
                schema_version: HELPER_SCHEMA_VERSION,
                request_id: "req".to_string(),
                status: HelperStatus::Error,
                region_set: None,
                overlay: None,
                error_code: Some(code),
                warnings: Vec::new(),
            };
            let error =
                validate_helper_response(response, "req", 4, 4, &ExtractionRequest::default())
                    .unwrap_err();
            assert!(error.to_string().contains(expected));
        }
        let package_error = helper_error_to_vision_error(Some(HelperErrorCode::PackageMissing));
        assert!(matches!(package_error, VisionError::PackageMissing(_)));
    }

    #[test]
    fn rejects_inconsistent_protocol_responses() {
        let mut response = error_response(HelperErrorCode::NoFace);
        response.error_code = None;
        assert!(matches!(
            validate_helper_response(response, "req", 4, 4, &ExtractionRequest::default()),
            Err(VisionError::HelperProtocolError(_))
        ));
        let mut response = ok_response(valid_region_set(10, 10));
        response.error_code = Some(HelperErrorCode::NoFace);
        assert!(matches!(
            validate_helper_response(response, "req", 10, 10, &ExtractionRequest::default()),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[test]
    fn validates_face_count_and_selected_index() {
        let mut region_set = valid_region_set(10, 10);
        region_set.extraction_quality.faces_detected = 2;
        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(_))
        ));
        let mut region_set = valid_region_set(10, 10);
        region_set.extraction_quality.selected_face_index = None;
        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(_))
        ));
        let mut region_set = valid_region_set(10, 10);
        region_set.extraction_quality.selected_face_index = Some(1);
        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[test]
    fn rejects_tiny_measured_region_area() {
        let tiny = RegionMask::Polygon {
            polygons: vec![vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 1.0, y: 0.0 },
                Point { x: 1.0, y: 1.0 },
            ]],
        };
        let region = RegionObservation {
            kind: RegionKind::Skin,
            status: RegionStatus::Measured,
            source: RegionSource::MediapipeLandmarks,
            confidence: 0.9,
            mask: Some(tiny),
            sample_hint: Some(1),
            approximate_reason: None,
            not_measured_reason: None,
        };
        assert!(matches!(
            validate_observation_from_helper(&region, 4, 4, 20),
            Err(VisionError::HelperProtocolError(_))
        ));
        let inflated_hint_region = RegionObservation {
            sample_hint: Some(10_000),
            ..region
        };
        assert!(matches!(
            validate_observation_from_helper(&inflated_hint_region, 4, 4, 20),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[test]
    fn rejects_tiny_rle_with_inflated_sample_hint() {
        let region = RegionObservation {
            kind: RegionKind::Skin,
            status: RegionStatus::Measured,
            source: RegionSource::MediapipeLandmarks,
            confidence: 0.9,
            mask: Some(RegionMask::RleBitmap {
                width: 10,
                height: 10,
                counts: vec![99, 1],
            }),
            sample_hint: Some(10_000),
            approximate_reason: None,
            not_measured_reason: None,
        };
        assert!(matches!(
            validate_observation_from_helper(&region, 10, 10, 20),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[test]
    fn accepts_sufficient_area_without_sample_hint() {
        let region = RegionObservation {
            kind: RegionKind::Skin,
            status: RegionStatus::Measured,
            source: RegionSource::MediapipeLandmarks,
            confidence: 0.9,
            mask: Some(RegionMask::Polygon {
                polygons: vec![vec![
                    Point { x: 0.0, y: 0.0 },
                    Point { x: 9.0, y: 0.0 },
                    Point { x: 9.0, y: 9.0 },
                    Point { x: 0.0, y: 9.0 },
                ]],
            }),
            sample_hint: None,
            approximate_reason: None,
            not_measured_reason: None,
        };
        assert!(validate_observation_from_helper(&region, 10, 10, 20).is_ok());
    }

    #[test]
    fn validates_overlay_opt_in_contract() {
        let response = ok_response(valid_region_set(10, 10));
        assert!(
            validate_helper_response(response, "req", 10, 10, &ExtractionRequest::default())
                .is_ok()
        );
        let mut response = ok_response(valid_region_set(10, 10));
        response.overlay = Some(OverlayArtifactSummary {
            output_path_redacted: "[REDACTED]".to_string(),
            dimensions: (4, 4),
            region_count: 5,
            color_key: Vec::new(),
        });
        assert!(matches!(
            validate_helper_response(response, "req", 10, 10, &ExtractionRequest::default()),
            Err(VisionError::HelperProtocolError(_))
        ));
        let request = ExtractionRequest {
            overlay_request: Some(crate::vision::OverlayRequest {
                output_path: "overlay.png".into(),
                include_points: true,
                include_labels: true,
            }),
            ..ExtractionRequest::default()
        };
        let mut response = ok_response(valid_region_set(10, 10));
        response.overlay = Some(OverlayArtifactSummary {
            output_path_redacted: "[REDACTED]".to_string(),
            dimensions: (4, 4),
            region_count: 5,
            color_key: Vec::new(),
        });
        assert!(validate_helper_response(response, "req", 10, 10, &request).is_ok());
    }

    #[test]
    fn rejects_repo_overlay_path_without_debug_override() {
        let workspace_overlay = workspace_root().unwrap().join("target/private-overlay.png");
        let mut request = ExtractionRequest {
            overlay_request: Some(crate::vision::OverlayRequest {
                output_path: workspace_overlay,
                include_points: false,
                include_labels: false,
            }),
            ..ExtractionRequest::default()
        };
        assert!(matches!(
            validate_overlay_output_policy(&request),
            Err(VisionError::HelperProtocolError(_))
        ));
        request.keep_debug_artifacts = true;
        assert!(validate_overlay_output_policy(&request).is_ok());
    }

    #[test]
    fn rejects_relative_parent_overlay_path_before_serialization() {
        let request = ExtractionRequest {
            overlay_request: Some(crate::vision::OverlayRequest {
                output_path: "../outside/overlay.png".into(),
                include_points: false,
                include_labels: false,
            }),
            ..ExtractionRequest::default()
        };
        assert!(matches!(
            validate_overlay_output_policy(&request),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[test]
    fn rejects_workspace_overlay_path_outside_crate_without_debug_override() {
        let workspace_overlay = workspace_root().unwrap().join("workspace-overlay.png");
        let mut request = ExtractionRequest {
            overlay_request: Some(crate::vision::OverlayRequest {
                output_path: workspace_overlay,
                include_points: false,
                include_labels: false,
            }),
            ..ExtractionRequest::default()
        };
        assert!(matches!(
            validate_overlay_output_policy(&request),
            Err(VisionError::HelperProtocolError(_))
        ));
        request.keep_debug_artifacts = true;
        assert!(validate_overlay_output_policy(&request).is_ok());
    }

    #[test]
    fn failed_process_errors_do_not_expose_raw_output_tokens() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        let response_path = temp.path().join("missing-response.json");
        let request_path = temp.path().join("request.json");
        fs::write(&request_path, b"{}").unwrap();
        fs::write(
            &script,
            "#!/bin/sh\necho sk-test-secret-api-key-token\necho /Users/alice/private/image.png raw stdout content 1>&2\necho stderr-gemini-api-key-like-token 1>&2\nexit 1\n",
        )
        .unwrap();
        make_executable(&script);
        let config = HelperBackendConfig {
            python_path: script,
            mediapipe_task_path: None,
            face_parsing_onnx_path: None,
            asset_manifest_path: None,
            timeout: DEFAULT_TIMEOUT,
        };
        let process = run_helper_process(&config, &request_path, &response_path).unwrap();
        let error = match read_helper_response(&response_path) {
            Ok(_) => unreachable!(),
            Err(_) => VisionError::HelperProtocolError(format!(
                "helper_process_failed_{}",
                process.diagnostics.public_code()
            )),
        };
        let display = error.to_string();
        assert!(display.contains("no_response"));
        assert!(!display.contains("sk-secret"));
        assert!(!display.contains("sk-test"));
        assert!(!display.contains("gemini"));
        assert!(!display.contains("api-key"));
        assert!(!display.contains("/Users"));
        assert!(!display.contains("alice"));
        assert!(!display.contains("image.png"));
        assert!(!display.contains("person"));
    }

    #[test]
    fn readiness_version_check_times_out() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        fs::write(&script, "#!/bin/sh\nsleep 2\n").unwrap();
        make_executable(&script);
        let started = Instant::now();
        let config = HelperBackendConfig {
            python_path: script,
            mediapipe_task_path: None,
            face_parsing_onnx_path: None,
            asset_manifest_path: None,
            timeout: Duration::from_millis(200),
        };
        let report = config.readiness_report();
        assert!(started.elapsed() < Duration::from_secs(1));
        assert!(report.python_version.is_none());
        assert!(!report.backend_available);
    }

    #[test]
    fn readiness_import_check_times_out() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        fs::write(
            &script,
            "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo 'Python 3.12.0'; exit 0; fi\nsleep 2\n",
        )
        .unwrap();
        make_executable(&script);
        let started = Instant::now();
        assert_eq!(
            sanitized_python_version(&script, Duration::from_millis(1_000)).as_deref(),
            Some("Python 3.12.0")
        );
        assert!(!python_can_import(
            &script,
            "mediapipe",
            Duration::from_millis(200)
        ));
        assert!(started.elapsed() < Duration::from_secs(2));
    }

    #[test]
    fn readiness_version_accepts_stderr_banner() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        fs::write(
            &script,
            "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo 'Python 3.12.0' 1>&2; exit 0; fi\nexit 1\n",
        )
        .unwrap();
        make_executable(&script);
        assert_eq!(
            sanitized_python_version(&script, DEFAULT_TIMEOUT).as_deref(),
            Some("Python 3.12.0")
        );
    }

    #[test]
    fn readiness_import_does_not_wait_for_descendant_holding_output() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        fs::write(
            &script,
            "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo 'Python 3.12.0'; exit 0; fi\nexec python3 -c 'import subprocess, sys; subprocess.Popen([\"sleep\", \"3\"]); sys.exit(1)'\n",
        )
        .unwrap();
        make_executable(&script);
        let started = Instant::now();
        assert!(!python_can_import(
            &script,
            "mediapipe",
            Duration::from_millis(1_500)
        ));
        assert!(started.elapsed() < Duration::from_secs(2));
    }

    #[test]
    fn readiness_version_does_not_wait_for_descendant_holding_stdout() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        fs::write(
            &script,
            "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then exec python3 -c 'import subprocess; subprocess.Popen([\"sleep\", \"3\"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True); print(\"Python 3.12.0\")'; fi\nexit 1\n",
        )
        .unwrap();
        make_executable(&script);
        let started = Instant::now();
        assert_eq!(
            sanitized_python_version(&script, Duration::from_millis(1_500)).as_deref(),
            Some("Python 3.12.0")
        );
        assert!(started.elapsed() < Duration::from_secs(2));
    }

    #[test]
    fn helper_process_does_not_wait_for_descendant_holding_output() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        let response_path = temp.path().join("response.json");
        let request_path = temp.path().join("request.json");
        fs::write(&request_path, b"{}").unwrap();
        fs::write(
            &script,
            "#!/bin/sh\nexec python3 -c 'import subprocess, sys; subprocess.Popen([\"sleep\", \"3\"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True); sys.exit(1)'\n",
        )
        .unwrap();
        make_executable(&script);
        let config = HelperBackendConfig {
            python_path: script,
            mediapipe_task_path: None,
            face_parsing_onnx_path: None,
            asset_manifest_path: None,
            timeout: Duration::from_millis(1_500),
        };
        let started = Instant::now();
        let process = run_helper_process(&config, &request_path, &response_path).unwrap();
        assert!(!process.exited_successfully);
        assert!(started.elapsed() < Duration::from_secs(2));
    }

    #[test]
    fn readiness_python_version_rejects_secret_or_path_tokens() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        fs::write(
            &script,
            "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo 'Python 3.12.0 sk-test GEMINI_API_KEY /Users/alice/private/image.png'; exit 0; fi\nexit 1\n",
        )
        .unwrap();
        make_executable(&script);
        let config = HelperBackendConfig {
            python_path: script,
            mediapipe_task_path: None,
            face_parsing_onnx_path: None,
            asset_manifest_path: None,
            timeout: Duration::from_millis(200),
        };
        let report = config.readiness_report();
        let debug = format!("{report:?}");
        assert!(report.python_version.is_none());
        assert!(!debug.contains("sk-test"));
        assert!(!debug.contains("GEMINI_API_KEY"));
        assert!(!debug.contains("/Users"));
        assert!(!debug.contains("image.png"));
    }

    #[test]
    fn helper_request_debug_redacts_paths() {
        let envelope = HelperRequestEnvelope {
            schema_version: HELPER_SCHEMA_VERSION,
            request_id: "req".to_string(),
            image_path: "/Users/private/input.png".into(),
            image_width: 10,
            image_height: 10,
            requested_regions: vec![RegionKind::Skin],
            min_region_pixels: 20,
            erode_pixels: 2,
            max_faces: 1,
            mediapipe_task_path: "/Users/private/model.task".into(),
            face_parsing_onnx_path: Some("/Users/private/parser.onnx".into()),
            asset_manifest_path: Some("/Users/private/manifest.json".into()),
            overlay_output_path: Some("/Users/private/overlay.png".into()),
            overlay_include_points: true,
            overlay_include_labels: true,
        };
        let debug = format!("{envelope:?}");
        assert!(debug.contains("configured_redacted"));
        assert!(!debug.contains("/Users"));
        assert!(!debug.contains("input.png"));
        assert!(!debug.contains("model.task"));
    }

    #[cfg(unix)]
    #[test]
    fn rejects_symlinked_overlay_parent_into_repo_without_debug_override() {
        let temp = tempfile::tempdir().unwrap();
        let link = temp.path().join("repo-link");
        std::os::unix::fs::symlink(env!("CARGO_MANIFEST_DIR"), &link).unwrap();
        let request = ExtractionRequest {
            overlay_request: Some(crate::vision::OverlayRequest {
                output_path: link.join("symlink-overlay.png"),
                include_points: false,
                include_labels: false,
            }),
            ..ExtractionRequest::default()
        };
        assert!(matches!(
            validate_overlay_output_policy(&request),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[cfg(unix)]
    #[test]
    fn rejects_existing_overlay_leaf_symlink_into_workspace() {
        let temp = tempfile::tempdir().unwrap();
        let overlay = temp.path().join("overlay.png");
        std::os::unix::fs::symlink(workspace_root().unwrap().join("Cargo.toml"), &overlay).unwrap();
        let request = ExtractionRequest {
            overlay_request: Some(crate::vision::OverlayRequest {
                output_path: overlay,
                include_points: false,
                include_labels: false,
            }),
            ..ExtractionRequest::default()
        };
        assert!(matches!(
            validate_overlay_output_policy(&request),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    #[cfg(unix)]
    #[test]
    fn rejects_existing_sidecar_leaf_symlink_into_workspace() {
        let temp = tempfile::tempdir().unwrap();
        let overlay = temp.path().join("overlay.png");
        let sidecar = overlay_sidecar_path(&overlay);
        std::os::unix::fs::symlink(workspace_root().unwrap().join("Cargo.toml"), &sidecar).unwrap();
        let request = ExtractionRequest {
            overlay_request: Some(crate::vision::OverlayRequest {
                output_path: overlay,
                include_points: false,
                include_labels: false,
            }),
            ..ExtractionRequest::default()
        };
        assert!(matches!(
            validate_overlay_output_policy(&request),
            Err(VisionError::HelperProtocolError(_))
        ));
    }

    fn expected_label_json() -> &'static str {
        r#"{"background":0,"skin":1,"l_brow":2,"r_brow":3,"l_eye":4,"r_eye":5,"eye_g":6,"l_ear":7,"r_ear":8,"ear_r":9,"nose":10,"mouth":11,"u_lip":12,"l_lip":13,"neck":14,"neck_l":15,"cloth":16,"hair":17,"hat":18}"#
    }

    fn parser_manifest_json(
        license: &str,
        approved_for_use: bool,
        sha256: &str,
        labels: &str,
    ) -> String {
        format!(
            r#"{{"face_parsing_onnx":{{"manifest_schema_version":1,"model_id":"{}","sha256":"{}","expected_sha256":"{}","license":"{}","license_id":"{}","approved_for_use":{},"model_type":"baseline_face_parser","source_url":"{}","source_revision":"{}","fallback_source":"{}","provenance":"{}","labels":{},"input":{{"width":512,"height":512}},"normalization":{{"mean":[0.485,0.456,0.406],"std":[0.229,0.224,0.225]}},"output":{{"type":"logits_argmax"}}}}}}"#,
            FACE_PARSER_MODEL_ID,
            sha256,
            FACE_PARSER_EXPECTED_SHA256,
            license,
            FACE_PARSER_LICENSE_ID,
            approved_for_use,
            FACE_PARSER_SOURCE_URL,
            FACE_PARSER_SOURCE_REVISION,
            FACE_PARSER_FALLBACK_SOURCE,
            FACE_PARSER_PROVENANCE,
            labels
        )
    }

    #[test]
    fn manifest_license_and_checksum_map_to_typed_errors() {
        let temp = tempfile::tempdir().unwrap();
        let parser_path = temp.path().join("parser.onnx");
        fs::write(&parser_path, b"parser").unwrap();
        let manifest_path = temp.path().join("manifest.json");
        fs::write(
            &manifest_path,
            parser_manifest_json(
                "",
                false,
                FACE_PARSER_EXPECTED_SHA256,
                expected_label_json(),
            ),
        )
        .unwrap();
        let config = HelperBackendConfig {
            python_path: PathBuf::from("python3"),
            mediapipe_task_path: Some(parser_path.clone()),
            face_parsing_onnx_path: Some(parser_path.clone()),
            asset_manifest_path: Some(manifest_path.clone()),
            timeout: DEFAULT_TIMEOUT,
        };
        let readiness = VisionReadinessReport {
            backend_available: false,
            python_version: Some("Python 3.12.0".to_string()),
            missing_packages: Vec::new(),
            missing_models: missing_model_assets(&config),
            warnings: Vec::new(),
        };
        assert!(matches!(
            first_readiness_error(readiness),
            VisionError::UnsupportedModelLicense
        ));
        fs::write(
            &manifest_path,
            parser_manifest_json(
                FACE_PARSER_LICENSE,
                true,
                FACE_PARSER_EXPECTED_SHA256,
                expected_label_json(),
            ),
        )
        .unwrap();
        let readiness = VisionReadinessReport {
            backend_available: false,
            python_version: Some("Python 3.12.0".to_string()),
            missing_packages: Vec::new(),
            missing_models: missing_model_assets(&config),
            warnings: Vec::new(),
        };
        assert!(matches!(
            first_readiness_error(readiness),
            VisionError::ModelChecksumMismatch
        ));
    }

    #[test]
    fn parser_manifest_rejects_baseline_facial_hair_labels() {
        let temp = tempfile::tempdir().unwrap();
        let parser_path = temp.path().join("parser.onnx");
        fs::write(&parser_path, b"parser").unwrap();
        let manifest_path = temp.path().join("manifest.json");
        fs::write(
            &manifest_path,
            parser_manifest_json(
                FACE_PARSER_LICENSE,
                true,
                FACE_PARSER_EXPECTED_SHA256,
                r#"{"background":0,"skin":1,"l_brow":2,"r_brow":3,"l_eye":4,"r_eye":5,"eye_g":6,"l_ear":7,"r_ear":8,"ear_r":9,"nose":10,"mouth":11,"u_lip":12,"l_lip":13,"neck":14,"neck_l":15,"cloth":16,"hair":17,"hat":18,"beard":19}"#,
            ),
        )
        .unwrap();
        let config = HelperBackendConfig {
            python_path: PathBuf::from("python3"),
            mediapipe_task_path: Some(parser_path.clone()),
            face_parsing_onnx_path: Some(parser_path),
            asset_manifest_path: Some(manifest_path),
            timeout: DEFAULT_TIMEOUT,
        };

        assert_eq!(
            missing_model_assets(&config),
            vec!["face_parsing_facial_hair_labels_rejected".to_string()]
        );
    }

    #[test]
    fn parser_manifest_rejects_wrong_provenance_metadata() {
        let temp = tempfile::tempdir().unwrap();
        let parser_path = temp.path().join("parser.onnx");
        fs::write(&parser_path, b"parser").unwrap();
        let manifest_path = temp.path().join("manifest.json");
        let manifest = parser_manifest_json(
            FACE_PARSER_LICENSE,
            true,
            FACE_PARSER_EXPECTED_SHA256,
            expected_label_json(),
        )
        .replace(FACE_PARSER_PROVENANCE, "unknown");
        fs::write(&manifest_path, manifest).unwrap();
        let config = HelperBackendConfig {
            python_path: PathBuf::from("python3"),
            mediapipe_task_path: Some(parser_path.clone()),
            face_parsing_onnx_path: Some(parser_path),
            asset_manifest_path: Some(manifest_path),
            timeout: DEFAULT_TIMEOUT,
        };

        assert!(
            missing_model_assets(&config)
                .iter()
                .any(|missing| missing == "face_parsing_license_not_approved")
        );
    }

    #[test]
    fn onnxruntime_is_required_for_helper_readiness() {
        let base_packages = required_python_packages(false)
            .into_iter()
            .map(|package| package.display_name)
            .collect::<Vec<_>>();
        let parser_packages = required_python_packages(true)
            .into_iter()
            .map(|package| package.display_name)
            .collect::<Vec<_>>();

        assert!(base_packages.contains(&"onnxruntime"));
        assert!(parser_packages.contains(&"onnxruntime"));
    }

    #[test]
    fn reads_protocol_error_response_after_nonzero_process_exit() {
        let temp = tempfile::tempdir().unwrap();
        let script = temp.path().join("python");
        let response_path = temp.path().join("response.json");
        let request_path = temp.path().join("request.json");
        fs::write(&request_path, b"{}").unwrap();
        let script_body = format!(
            "#!/bin/sh\ncat > '{}' <<'JSON'\n{{\"schema_version\":1,\"request_id\":\"unknown\",\"status\":\"error\",\"region_set\":null,\"overlay\":null,\"error_code\":\"invalid_request\",\"warnings\":[]}}\nJSON\nexit 1\n",
            response_path.display()
        );
        fs::write(&script, script_body).unwrap();
        make_executable(&script);
        let config = HelperBackendConfig {
            python_path: script,
            mediapipe_task_path: None,
            face_parsing_onnx_path: None,
            asset_manifest_path: None,
            timeout: DEFAULT_TIMEOUT,
        };
        let process = run_helper_process(&config, &request_path, &response_path).unwrap();
        assert!(!process.exited_successfully);
        let response = read_helper_response(&response_path).unwrap();
        assert!(matches!(
            response.error_code,
            Some(HelperErrorCode::InvalidRequest)
        ));
    }

    #[test]
    fn parse_time_model_missing_preserves_request_id_and_typed_error() {
        let temp = tempfile::tempdir().unwrap();
        let request_path = temp.path().join("request.json");
        let response_path = temp.path().join("response.json");
        let image_path = temp.path().join("input.png");
        fs::write(&image_path, b"placeholder").unwrap();
        fs::write(
            &request_path,
            serde_json::json!({
                "schema_version": HELPER_SCHEMA_VERSION,
                "request_id": "req",
                "image_path": image_path,
                "image_width": 10,
                "image_height": 10,
                "requested_regions": ["skin"],
                "min_region_pixels": 20,
                "erode_pixels": 2,
                "max_faces": 1,
                "mediapipe_task_path": temp.path().join("missing.task"),
                "face_parsing_onnx_path": null,
                "overlay_output_path": null,
                "overlay_include_points": false,
                "overlay_include_labels": false
            })
            .to_string(),
        )
        .unwrap();
        let config = HelperBackendConfig {
            python_path: PathBuf::from("python3"),
            mediapipe_task_path: None,
            face_parsing_onnx_path: None,
            asset_manifest_path: None,
            timeout: DEFAULT_TIMEOUT,
        };
        let Ok(_) = run_helper_process(&config, &request_path, &response_path) else {
            return;
        };
        let response = read_helper_response(&response_path).unwrap();
        assert_eq!(response.request_id, "req");
        assert!(matches!(
            validate_helper_response(response, "req", 10, 10, &ExtractionRequest::default()),
            Err(VisionError::ModelMissing(_))
        ));
    }

    #[test]
    fn requested_hair_and_beard_observations_are_required() {
        let mut missing_hair = valid_region_set(10, 10);
        missing_hair
            .regions
            .retain(|region| region.kind != RegionKind::Hair);
        assert!(matches!(
            validate_helper_response(
                ok_response(missing_hair),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(_))
        ));

        let mut missing_beard = valid_region_set(10, 10);
        missing_beard
            .regions
            .retain(|region| region.kind != RegionKind::Beard);
        assert!(matches!(
            validate_helper_response(
                ok_response(missing_beard),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(_))
        ));

        assert!(
            validate_helper_response(
                ok_response(valid_region_set(10, 10)),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            )
            .is_ok()
        );
    }

    #[test]
    fn low_evidence_beard_observation_satisfies_requested_beard_contract() {
        let mut region_set = valid_region_set(10, 10);
        let beard = region_set
            .regions
            .iter_mut()
            .find(|region| region.kind == RegionKind::Beard)
            .expect("valid fixture should include beard");
        beard.status = RegionStatus::LowEvidence;
        beard.confidence = 0.35;
        beard.approximate_reason = Some("clean_shaven_or_low_stubble_evidence".to_string());

        assert!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            )
            .is_ok()
        );
    }

    #[test]
    fn stale_not_configured_hair_response_is_rejected() {
        let mut region_set = valid_region_set(10, 10);
        let hair = region_set
            .regions
            .iter_mut()
            .find(|region| region.kind == RegionKind::Hair)
            .expect("valid fixture should include hair");
        hair.status = RegionStatus::NotMeasured {
            reason: "semantic_parser_not_configured".to_string(),
        };
        hair.not_measured_reason = Some("semantic_parser_not_configured".to_string());

        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(reason))
                if reason == "semantic_parser_hair_response_invalid"
        ));
    }

    #[test]
    fn measured_hair_must_come_from_parser_mask() {
        let mut region_set = valid_region_set(10, 10);
        set_hair_measured_from_parser(&mut region_set);

        assert!(
            validate_helper_response(
                ok_response(region_set.clone()),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            )
            .is_ok()
        );

        let hair = hair_region_mut(&mut region_set);
        hair.source = RegionSource::MediapipeLandmarks;

        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(reason))
                if reason == "semantic_parser_hair_response_invalid"
        ));
    }

    #[test]
    fn not_measured_hair_requires_matching_low_evidence_reasons() {
        let aligned_region_set = valid_region_set(10, 10);
        assert!(
            validate_helper_response(
                ok_response(aligned_region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            )
            .is_ok()
        );

        for (status_reason, duplicated_reason) in [
            (
                "semantic_parser_not_configured",
                Some("semantic_parser_hair_evidence_low"),
            ),
            (
                "semantic_parser_hair_evidence_low",
                Some("semantic_parser_not_configured"),
            ),
            ("semantic_parser_hair_evidence_low", None),
        ] {
            let mut region_set = valid_region_set(10, 10);
            let hair = hair_region_mut(&mut region_set);
            hair.status = RegionStatus::NotMeasured {
                reason: status_reason.to_string(),
            };
            hair.not_measured_reason = duplicated_reason.map(str::to_string);

            assert!(matches!(
                validate_helper_response(
                    ok_response(region_set),
                    "req",
                    10,
                    10,
                    &ExtractionRequest::default()
                ),
                Err(VisionError::HelperProtocolError(reason))
                    if reason == "semantic_parser_hair_response_invalid"
            ));
        }
    }

    #[test]
    fn measured_hair_rejects_stale_not_measured_reason() {
        let mut region_set = valid_region_set(10, 10);
        set_hair_measured_from_parser(&mut region_set);
        hair_region_mut(&mut region_set).not_measured_reason =
            Some("semantic_parser_not_configured".to_string());

        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(reason))
                if reason == "semantic_parser_hair_response_invalid"
        ));
    }

    #[test]
    fn duplicate_hair_observations_are_rejected_even_when_first_is_valid() {
        let mut region_set = valid_region_set(10, 10);
        set_hair_measured_from_parser(&mut region_set);
        let mut duplicate_hair = hair_region_mut(&mut region_set).clone();
        duplicate_hair.source = RegionSource::MediapipeLandmarks;
        region_set.regions.push(duplicate_hair);

        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(reason))
                if reason == "semantic_parser_hair_duplicate"
        ));
    }

    #[test]
    fn duplicate_valid_parser_hair_observations_are_rejected() {
        let mut region_set = valid_region_set(10, 10);
        set_hair_measured_from_parser(&mut region_set);
        let duplicate_hair = hair_region_mut(&mut region_set).clone();
        region_set.regions.push(duplicate_hair);

        assert!(matches!(
            validate_helper_response(
                ok_response(region_set),
                "req",
                10,
                10,
                &ExtractionRequest::default()
            ),
            Err(VisionError::HelperProtocolError(reason))
                if reason == "semantic_parser_hair_duplicate"
        ));
    }

    fn ok_response(region_set: RegionSet) -> HelperResponseEnvelope {
        HelperResponseEnvelope {
            schema_version: HELPER_SCHEMA_VERSION,
            request_id: "req".to_string(),
            status: HelperStatus::Ok,
            region_set: Some(region_set),
            overlay: None,
            error_code: None,
            warnings: Vec::new(),
        }
    }

    fn error_response(error_code: HelperErrorCode) -> HelperResponseEnvelope {
        HelperResponseEnvelope {
            schema_version: HELPER_SCHEMA_VERSION,
            request_id: "req".to_string(),
            status: HelperStatus::Error,
            region_set: None,
            overlay: None,
            error_code: Some(error_code),
            warnings: Vec::new(),
        }
    }

    fn valid_region_set(width: u32, height: u32) -> RegionSet {
        let square = RegionMask::Polygon {
            polygons: vec![vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 9.0, y: 0.0 },
                Point { x: 9.0, y: 9.0 },
                Point { x: 0.0, y: 9.0 },
            ]],
        };
        let region = |kind| RegionObservation {
            kind,
            status: RegionStatus::Measured,
            source: RegionSource::MediapipeLandmarks,
            confidence: 0.9,
            mask: Some(square.clone()),
            sample_hint: Some(20),
            approximate_reason: None,
            not_measured_reason: None,
        };
        let hair = RegionObservation {
            kind: RegionKind::Hair,
            status: RegionStatus::NotMeasured {
                reason: "semantic_parser_hair_evidence_low".to_string(),
            },
            source: RegionSource::NotMeasured,
            confidence: 0.0,
            mask: None,
            sample_hint: None,
            approximate_reason: None,
            not_measured_reason: Some("semantic_parser_hair_evidence_low".to_string()),
        };
        let beard = RegionObservation {
            kind: RegionKind::Beard,
            status: RegionStatus::Approximate,
            source: RegionSource::Approximation,
            confidence: 0.55,
            mask: Some(square.clone()),
            sample_hint: Some(20),
            approximate_reason: Some("landmark_lower_face_proxy".to_string()),
            not_measured_reason: None,
        };
        RegionSet {
            image_width: width,
            image_height: height,
            regions: vec![
                region(RegionKind::Skin),
                region(RegionKind::Brow),
                region(RegionKind::Iris),
                region(RegionKind::Sclera),
                region(RegionKind::Lip),
                hair,
                beard,
            ],
            extraction_quality: ExtractionQualityReport {
                faces_detected: 1,
                selected_face_index: Some(0),
                backend: "mediapipe".to_string(),
                warnings: Vec::new(),
            },
            warnings: Vec::new(),
        }
    }

    fn hair_region_mut(region_set: &mut RegionSet) -> &mut RegionObservation {
        region_set
            .regions
            .iter_mut()
            .find(|region| region.kind == RegionKind::Hair)
            .expect("valid fixture should include hair")
    }

    fn set_hair_measured_from_parser(region_set: &mut RegionSet) {
        let square = RegionMask::Polygon {
            polygons: vec![vec![
                Point { x: 0.0, y: 0.0 },
                Point { x: 9.0, y: 0.0 },
                Point { x: 9.0, y: 9.0 },
                Point { x: 0.0, y: 9.0 },
            ]],
        };
        let hair = hair_region_mut(region_set);
        hair.status = RegionStatus::Measured;
        hair.source = RegionSource::ParserMask;
        hair.confidence = 0.78;
        hair.mask = Some(square);
        hair.sample_hint = Some(20);
        hair.not_measured_reason = None;
    }

    fn make_executable(path: &Path) {
        let mut permissions = fs::metadata(path).unwrap().permissions();
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            permissions.set_mode(0o700);
        }
        fs::set_permissions(path, permissions).unwrap();
    }
}
