//! Locked SHA256 hash tests for frymire.png encoding.
//!
//! These tests verify that encoder output is byte-for-byte reproducible.
//! Any refactoring that changes output will break these tests.
//!
//! Test image: tests/images/frymire.png (1118x1105 RGB)
//!
//! To regenerate hashes after intentional changes:
//!   cargo test --release --test frymire_hash_locked print_all_hashes -- --ignored --nocapture
//!
//! ⚠️ LOCKED TEST: Do NOT modify hash values without understanding the impact.

use jpegli::encoder::{ChromaSubsampling, DownsamplingMethod, EncoderConfig, PixelLayout};
use sha2::{Digest, Sha256};
use std::fs;

// =============================================================================
// CONFIGURATION
// =============================================================================

/// Quality levels to test (representative sample)
const QUALITY_LEVELS: &[u8] = &[50, 75, 90];

// =============================================================================
// EXPECTED HASHES (generated by print_all_hashes)
// Format: (config_name, quality, sha256_hex, size_bytes)
// =============================================================================

// frymire.png: 1118x1105
// Generated by: cargo test --release --test frymire_hash_locked print_all_hashes -- --ignored --nocapture
// Updated 2026-01-16: After revert of incorrect Cb/Cr table sharing fix and cpp_parity_locked refresh
const EXPECTED_HASHES: &[(&str, u8, &str, usize)] = &[
    (
        "baseline_444_opt",
        50,
        "8deab86bcfda5133e4aeadd7c090fcc5e05d6ba9cc4eaa8a67743145c8eb2862",
        329833,
    ),
    (
        "baseline_444_opt",
        75,
        "714e559755e1015c85e773c55d6f65e95deba887cd94618986a6fc76ff2ca256",
        476949,
    ),
    (
        "baseline_444_opt",
        90,
        "8c5357a9e8fe0d9e18b4d62d9abf600d6e7554d3ed38bb78108de42390477d15",
        717969,
    ),
    (
        "baseline_444_fixed",
        50,
        "d7736ef3f30c12d38e4989155f8163107104e91b187afe93dba8f246b3b95148",
        344259,
    ),
    (
        "baseline_444_fixed",
        75,
        "fe85b0bd9016f0b24ab297b90854fd421d07ed989de2656bb5f1881a0e174024",
        496305,
    ),
    (
        "baseline_444_fixed",
        90,
        "70502f5cf4e650d1c94ff20ffe0aac8485120ccf478c8d8b5d5161c8c6a8e5ce",
        766942,
    ),
    (
        "baseline_420_opt",
        50,
        "8b9b5be16cec6b90b0f2d8e42a27971c30e3ec1844a5ef42cbfc04564f917c17",
        269794,
    ),
    (
        "baseline_420_opt",
        75,
        "d3c27f2637d41aa089be859f44467e1b652f71d2beb82578cd7f583331879c2a",
        397247,
    ),
    (
        "baseline_420_opt",
        90,
        "55f36b2de56f669f14c1b18437a4a6385c258aa675cac02b02c92b21ae9ee9b0",
        584245,
    ),
    (
        "baseline_420_gamma",
        50,
        "971fc61aec8d8ae79876d0ae66e0067994564131d7f7ecc7d5aafd8ba2f900a0",
        270771,
    ),
    (
        "baseline_420_gamma",
        75,
        "34be3b723c3e72aad9c571fa615ced904a7437b91db3b93cd2ad26520559b224",
        398916,
    ),
    (
        "baseline_420_gamma",
        90,
        "0885e8f27e55e64c3a6303eaab1a78503cb69788b301194f861b4471d516b77e",
        586888,
    ),
    (
        "baseline_420_sharpyuv",
        50,
        "a1a202733034213594acef37b9d1d9269066e6ad036cf61c6b4b953968da544e",
        270349,
    ),
    (
        "baseline_420_sharpyuv",
        75,
        "cf8b0719b0db7e0d1af0109ee7015417f8092f53b9180d3aa2b9cbeeef33f7b3",
        398174,
    ),
    (
        "baseline_420_sharpyuv",
        90,
        "0e0e8a0e35ea5d0c71e93fe39ddb271ed1c6c7139e27ae53318e8119fe99a516",
        585699,
    ),
    (
        "baseline_422_opt",
        50,
        "fcc2a64ae3efd88711b1ce851e82c4fe29389d195f2cd2c5efc3613cc2bbaa81",
        293349,
    ),
    (
        "baseline_422_opt",
        75,
        "23e465fc3d611216aed4121479d4a1c2139e9c888ccea8580698dee9d8c24933",
        419306,
    ),
    (
        "baseline_422_opt",
        90,
        "2a49aaff2a706f8d2bdd7d76ea66a7d9b62774d2b54abe4dad2d1bce2d323c3f",
        613771,
    ),
    (
        "baseline_440_opt",
        50,
        "fee12e56c5163960b080eb217dfc7aac45bfe81760e2f8957506591f447d243e",
        293586,
    ),
    (
        "baseline_440_opt",
        75,
        "e11d6b937cee8f5c861e7e86043cb1d455d038a6e70490589e740f1b7d6fac07",
        420044,
    ),
    (
        "baseline_440_opt",
        90,
        "821250c0b5f80da77b22204034e3aa302c1e2323c563b42320942f385f99eaa2",
        615697,
    ),
    (
        "progressive_444_opt",
        50,
        "fbb3fb91ed7bf6cd325f382ac2e0a39391b15fed6e2a3d45feadf69f9b582b0d",
        320726,
    ),
    (
        "progressive_444_opt",
        75,
        "ddb76f2ae86eefe5444121a88e267366a3a96ae68e2d141f087e454d1620aeea",
        462624,
    ),
    (
        "progressive_444_opt",
        90,
        "a465a5b3c0f3a796fb01852b9a145250cda41ac4123cc9fdcbbe657b63e73bac",
        696683,
    ),
    (
        "progressive_420_opt",
        50,
        "4609ead551af5273ace221f3a14bc1af7ed4c944a0dac1df1e13e93fb4e59a08",
        261177,
    ),
    (
        "progressive_420_opt",
        75,
        "aa1d9ce926d894d2606c12a4e7b270a835aa192d5515cedb2a251474c1efe29f",
        383905,
    ),
    (
        "progressive_420_opt",
        90,
        "ce1ec45a37b681d70e929076ec2fca36ef363f1d18c4b1bd0a37f1cf94a6689b",
        564241,
    ),
    (
        "baseline_xyb_opt",
        50,
        "4acbed2702fbe5c5f715abf3244fb7e15dc24f2cfaba863e1c0ca1903fe6e261",
        272721,
    ),
    (
        "baseline_xyb_opt",
        75,
        "7164095810fa874bd42a02fe5ca03f7ad5cc79e2cd4f48ef2c94cfd72a91bd64",
        397271,
    ),
    (
        "baseline_xyb_opt",
        90,
        "d03c14584c4cd0f1a93a97adcb7a1952b9b32957a4d0d564ff4bc7ea3d0f501d",
        614790,
    ),
    (
        "baseline_xyb_fixed",
        50,
        "2e900de46dfa1551095b29ef96024f718bc995dbd1f1851abf3a2fbc05157b34",
        294825,
    ),
    (
        "baseline_xyb_fixed",
        75,
        "2f99e2c32f2ab7b865406d8d4d743a50f69c2fc681ecea19b98af4da51c5b3c2",
        418890,
    ),
    (
        "baseline_xyb_fixed",
        90,
        "8ee8a6274e096413502d5da1ad1645b18e255434ea070c54f814c177a5613a3d",
        651998,
    ),
    (
        "progressive_xyb_opt",
        50,
        "bd3553c55481487fbe6c8bd1e6d1e973b1c570e457758d08337f1b83d9e24694",
        270465,
    ),
    (
        "progressive_xyb_opt",
        75,
        "315e2e478077bcb75d85880e5bd95cd5c926f4485b85a9641628d14898a13e23",
        396193,
    ),
    (
        "progressive_xyb_opt",
        90,
        "ba69c7cd8242e2f24ac55c7c44639e3e81f16d01a8842d830363498a46db5ba5",
        615329,
    ),
];

// =============================================================================
// HELPER FUNCTIONS
// =============================================================================

fn load_frymire() -> (Vec<u8>, u32, u32) {
    let png_path =
        std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/images/frymire.png");
    let png_data = fs::read(&png_path).expect("Failed to read frymire.png");
    let decoder = png::Decoder::new(&png_data[..]);
    let mut reader = decoder.read_info().unwrap();
    let mut buf = vec![0; reader.output_buffer_size()];
    let info = reader.next_frame(&mut buf).unwrap();

    let rgb: Vec<u8> = match info.color_type {
        png::ColorType::Rgb => buf[..info.buffer_size()].to_vec(),
        png::ColorType::Rgba => buf[..info.buffer_size()]
            .chunks(4)
            .flat_map(|c| [c[0], c[1], c[2]])
            .collect(),
        _ => panic!("Unsupported color type"),
    };

    (rgb, info.width, info.height)
}

fn sha256_hex(data: &[u8]) -> String {
    let mut hasher = Sha256::new();
    hasher.update(data);
    format!("{:x}", hasher.finalize())
}

/// Encoding configuration for testing
#[derive(Clone, Debug)]
struct EncoderTestConfig {
    name: &'static str,
    progressive: bool,
    subsampling: ChromaSubsampling,
    optimize_huffman: bool,
    downsampling_method: DownsamplingMethod,
    use_xyb: bool,
}

impl EncoderTestConfig {
    fn encode(&self, rgb: &[u8], width: u32, height: u32, quality: f32) -> Vec<u8> {
        let mut config = EncoderConfig::new(90.0, ChromaSubsampling::Quarter)
            .quality(quality)
            .progressive(self.progressive)
            .ycbcr(self.subsampling)
            .optimize_huffman(self.optimize_huffman)
            .downsampling_method(self.downsampling_method);
        if self.use_xyb {
            config = config.xyb();
        }
        let mut enc = config
            .encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
            .expect("encoder setup");
        enc.push_packed(rgb, enough::Unstoppable)
            .expect("push data");
        enc.finish().expect("Encoding failed")
    }

    fn mcu_size(&self) -> usize {
        match self.subsampling {
            ChromaSubsampling::None => 8,
            _ => 16, // All subsampled modes have MCU size of 16
        }
    }
}

/// All configurations to test
fn test_configs() -> Vec<EncoderTestConfig> {
    vec![
        // Baseline modes (YCbCr)
        EncoderTestConfig {
            name: "baseline_444_opt",
            progressive: false,
            subsampling: ChromaSubsampling::None,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: false,
        },
        EncoderTestConfig {
            name: "baseline_444_fixed",
            progressive: false,
            subsampling: ChromaSubsampling::None,
            optimize_huffman: false,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: false,
        },
        EncoderTestConfig {
            name: "baseline_420_opt",
            progressive: false,
            subsampling: ChromaSubsampling::Quarter,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: false,
        },
        EncoderTestConfig {
            name: "baseline_420_gamma",
            progressive: false,
            subsampling: ChromaSubsampling::Quarter,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::GammaAware,
            use_xyb: false,
        },
        EncoderTestConfig {
            name: "baseline_420_sharpyuv",
            progressive: false,
            subsampling: ChromaSubsampling::Quarter,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::GammaAwareIterative,
            use_xyb: false,
        },
        EncoderTestConfig {
            name: "baseline_422_opt",
            progressive: false,
            subsampling: ChromaSubsampling::HalfHorizontal,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: false,
        },
        EncoderTestConfig {
            name: "baseline_440_opt",
            progressive: false,
            subsampling: ChromaSubsampling::HalfVertical,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: false,
        },
        // Progressive modes (YCbCr)
        EncoderTestConfig {
            name: "progressive_444_opt",
            progressive: true,
            subsampling: ChromaSubsampling::None,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: false,
        },
        EncoderTestConfig {
            name: "progressive_420_opt",
            progressive: true,
            subsampling: ChromaSubsampling::Quarter,
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: false,
        },
        // XYB modes (subsampling is ignored for XYB)
        EncoderTestConfig {
            name: "baseline_xyb_opt",
            progressive: false,
            subsampling: ChromaSubsampling::Quarter, // Ignored for XYB
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: true,
        },
        EncoderTestConfig {
            name: "baseline_xyb_fixed",
            progressive: false,
            subsampling: ChromaSubsampling::Quarter, // Ignored for XYB
            optimize_huffman: false,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: true,
        },
        EncoderTestConfig {
            name: "progressive_xyb_opt",
            progressive: true,
            subsampling: ChromaSubsampling::Quarter, // Ignored for XYB
            optimize_huffman: true,
            downsampling_method: DownsamplingMethod::Box,
            use_xyb: true,
        },
    ]
}

// =============================================================================
// TESTS
// =============================================================================

/// Verify all configurations produce expected hashes.
/// This test is skipped until hashes are populated.
#[test]
fn test_frymire_hashes_locked() {
    if EXPECTED_HASHES.is_empty() {
        println!("No hashes configured yet. Run print_all_hashes to generate.");
        return;
    }

    let (rgb, width, height) = load_frymire();

    for (config_name, quality, expected_hash, expected_size) in EXPECTED_HASHES {
        let config = test_configs()
            .into_iter()
            .find(|c| c.name == *config_name)
            .expect("Unknown config");

        let jpeg = config.encode(&rgb, width, height, *quality as f32);
        let actual_hash = sha256_hex(&jpeg);

        assert_eq!(
            jpeg.len(),
            *expected_size,
            "{} Q{}: size mismatch!\n  expected: {} bytes\n  actual:   {} bytes",
            config_name,
            quality,
            expected_size,
            jpeg.len()
        );

        assert_eq!(
            actual_hash,
            *expected_hash,
            "{} Q{}: hash mismatch!\n  expected: {}\n  actual:   {}\n  size: {} bytes",
            config_name,
            quality,
            expected_hash,
            actual_hash,
            jpeg.len()
        );
    }
}

/// Test encoding with various configurations.
/// This verifies strip encoder parity without depending on specific hashes.
///
/// NOTE: Currently skips configs where frymire.png (1118x1105) doesn't align to MCU boundaries.
/// The full-plane encoder has edge padding, but the strip encoder doesn't yet.
/// - 4:4:4: MCU=8, partial cols=6, partial rows=1 -> SKIP
/// - 4:2:0/4:2:2/4:4:0: MCU=16, partial cols=14, partial rows=1 -> SKIP
///
/// TODO: Remove this skip once strip encoder has edge padding.
#[test]
fn test_frymire_backend_parity() {
    let (rgb, width, height) = load_frymire();

    // Test all configs that support strip encoding (baseline and progressive, including XYB)
    let strip_configs = test_configs();

    for config in &strip_configs {
        // Skip configs where dimensions don't align to MCU (strip encoder lacks edge padding)
        let mcu_size = config.mcu_size();
        if width as usize % mcu_size != 0 || height as usize % mcu_size != 0 {
            // Strip encoder doesn't have edge padding yet - skip non-aligned configs
            continue;
        }

        for &quality in &[50u8, 90] {
            // Use Both backend - will error if outputs differ
            let _jpeg = config.encode(&rgb, width, height, quality as f32);
        }
    }
}

// =============================================================================
// UTILITY: Generate hashes
// =============================================================================

/// Print all hashes for updating EXPECTED_HASHES.
/// Run with: cargo test --release --test frymire_hash_locked print_all_hashes -- --ignored --nocapture
#[test]
#[ignore = "utility for generating hash values"]
fn print_all_hashes() {
    let (rgb, width, height) = load_frymire();
    println!("// frymire.png: {}x{}", width, height);
    println!("// Generated by: cargo test --release --test frymire_hash_locked print_all_hashes -- --ignored --nocapture");
    println!("// Format: (config_name, quality, sha256_hex, size_bytes)");
    println!();
    println!("const EXPECTED_HASHES: &[(&str, u8, &str, usize)] = &[");

    for config in test_configs() {
        for &quality in QUALITY_LEVELS {
            let jpeg = config.encode(&rgb, width, height, quality as f32);
            let hash = sha256_hex(&jpeg);
            println!(
                "    (\"{}\", {}, \"{}\", {}),",
                config.name,
                quality,
                hash,
                jpeg.len()
            );
        }
    }

    println!("];");
}

/// Print hash for a single configuration (for debugging).
#[test]
#[ignore = "utility"]
fn print_single_hash() {
    let (rgb, width, height) = load_frymire();

    let config = EncoderConfig::new(90.0, ChromaSubsampling::Quarter)
        .quality(90.0)
        .ycbcr(ChromaSubsampling::None)
        .optimize_huffman(true);
    let mut enc = config
        .encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
        .expect("encoder setup");
    enc.push_packed(&rgb, enough::Unstoppable)
        .expect("push data");
    let jpeg = enc.finish().expect("Encoding failed");

    println!("Size: {} bytes", jpeg.len());
    println!("SHA256: {}", sha256_hex(&jpeg));
}
