use std::path::Path;

use bone_render::{PixelDiff, PixelDiffThreshold, decode_png};

const GOLDEN_DIFF_TOLERANCE: f64 = 16.0 / 255.0;
const GOLDEN_MISMATCH_BUDGET_DIVISOR: u64 = 1000;

pub fn check_golden(label: &str, actual_png: &Path, golden: &Path, update_env: &str) {
    let bytes = match std::fs::read(actual_png) {
        Ok(b) => b,
        Err(e) => panic!("read {}: {e}", actual_png.display()),
    };
    if std::env::var(update_env).is_ok_and(|value| !matches!(value.as_str(), "" | "0")) {
        if let Some(parent) = golden.parent()
            && let Err(e) = std::fs::create_dir_all(parent)
        {
            panic!("create {}: {e}", parent.display());
        }
        if let Err(e) = std::fs::write(golden, &bytes) {
            panic!("write {}: {e}", golden.display());
        }
        return;
    }
    let Ok(golden_bytes) = std::fs::read(golden) else {
        panic!(
            "golden missing at {}: rerun with {update_env}=1 to generate",
            golden.display(),
        );
    };
    let ((actual_extent, actual_rgba), (golden_extent, golden_rgba)) =
        match (decode_png(&bytes), decode_png(&golden_bytes)) {
            (Ok(a), Ok(g)) => (a, g),
            (Err(e), _) | (_, Err(e)) => panic!("decode png: {e}"),
        };
    assert_eq!(actual_extent, golden_extent, "{label} golden extent drift");
    let threshold = PixelDiffThreshold::new(GOLDEN_DIFF_TOLERANCE);
    let report =
        match PixelDiff::compare_bytes(actual_extent, &actual_rgba, &golden_rgba, threshold) {
            Ok(r) => r,
            Err(e) => panic!("PixelDiff rejected inputs: {e}"),
        };
    let budget = actual_extent.pixel_count() / GOLDEN_MISMATCH_BUDGET_DIVISOR;
    assert!(
        report.within_budget(budget),
        "{label} frame drifted from golden: {} of {} pixels over threshold (budget {budget}), worst {:?}",
        report.over_threshold(),
        actual_extent.pixel_count(),
        report.worst(),
    );
}
