//! Layer 5 architecture-hash backward-incompatibility test.
//!
//! Confirms that a synthetic "pre-MoE checkpoint" with a zeroed-out
//! `dqn.arch_hash` field is rejected by `DQNConfig::validate_checkpoint_metadata`
//! with a clear architecture-mismatch error.
//!
//! This is a purely synthetic CPU-only test — no CUDA, no file I/O, no smoke log.
//! It validates the no-fallback contract per spec §6.4: old checkpoints fail loudly,
//! no silent loading of incompatible weights.
//!
//! Spec: docs/superpowers/specs/2026-04-27-moe-regime-redesign-design.md §8.5.

use ml_dqn::dqn::DQNConfig;
use std::collections::HashMap;

/// Build checkpoint metadata identical to what a real checkpoint would store
/// but with a deliberately wrong `dqn.arch_hash` (64 hex zeros).
///
/// The all-zero hash cannot match any real config: SHA-256 never produces
/// an all-zero output for a well-formed input. Any current `DQNConfig`
/// will produce a different hash and the validation must reject it.
fn fake_old_metadata(config: &DQNConfig) -> HashMap<String, String> {
    let mut meta = config.checkpoint_metadata();
    meta.insert(
        "dqn.arch_hash".to_owned(),
        "0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
    );
    meta
}

#[test]
fn architecture_hash_old_checkpoint_rejected() {
    let config = DQNConfig::emergency_safe_defaults();
    let fake_metadata = fake_old_metadata(&config);

    let result = config.validate_checkpoint_metadata(&Some(fake_metadata));

    assert!(
        result.is_err(),
        "validate_checkpoint_metadata must reject a checkpoint whose arch_hash does not \
         match the current config — the no-fallback contract requires loud failure, \
         not silent loading of incompatible weights",
    );

    let err_msg = format!("{}", result.unwrap_err());
    assert!(
        err_msg.contains("Architecture mismatch")
            || err_msg.contains("arch_hash")
            || err_msg.contains("fingerprint"),
        "Error message must mention architecture or fingerprint mismatch so that \
         the caller understands why loading failed. Got: {}",
        err_msg,
    );
}

#[test]
fn architecture_hash_missing_metadata_rejected() {
    let config = DQNConfig::emergency_safe_defaults();

    let result = config.validate_checkpoint_metadata(&None);

    assert!(
        result.is_err(),
        "validate_checkpoint_metadata must reject a checkpoint with no metadata at all",
    );

    let err_msg = format!("{}", result.unwrap_err());
    assert!(
        err_msg.contains("no architecture metadata")
            || err_msg.contains("arch_hash")
            || err_msg.contains("metadata"),
        "Error message must describe the missing-metadata condition. Got: {}",
        err_msg,
    );
}

#[test]
fn architecture_hash_matching_metadata_accepted() {
    let config = DQNConfig::emergency_safe_defaults();
    let valid_metadata = config.checkpoint_metadata();

    let result = config.validate_checkpoint_metadata(&Some(valid_metadata));

    assert!(
        result.is_ok(),
        "validate_checkpoint_metadata must accept metadata generated by the same config. \
         Got error: {:?}",
        result.unwrap_err(),
    );
}

#[test]
fn architecture_hash_missing_arch_hash_field_rejected() {
    let config = DQNConfig::emergency_safe_defaults();
    let mut meta = config.checkpoint_metadata();
    meta.remove("dqn.arch_hash");

    let result = config.validate_checkpoint_metadata(&Some(meta));

    assert!(
        result.is_err(),
        "validate_checkpoint_metadata must reject metadata that is present but \
         missing the dqn.arch_hash field",
    );
}
