//! Tests that `NoesisDiagnostics` mirrors Noesis allocator counters into the
//! resource each frame.
//!
//! The all-zero `Default` is the negative control: a broken or missing refresh
//! leaves every counter at 0. Two snapshots (early and late) prove the refresh
//! runs every frame, not one-shot. Font-free XAML so the scene builds without a
//! font gate.

use std::sync::{Arc, Mutex};
use std::time::Duration;

use bevy::app::{AppExit, ScheduleRunnerPlugin};
use bevy::prelude::*;
use bevy::window::{ExitCondition, WindowPlugin};
use noesis_bevy::{NoesisCamera, NoesisDiagnostics, NoesisPlugin, NoesisView, XamlRegistry};

const EARLY_AT_FRAME: usize = 3;
const SAMPLE_AT_FRAME: usize = 40;
const EXIT_AT_FRAME: usize = 50;

const XAML: &str = r##"<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Width="64" Height="32">
  <Border x:Name="Panel" Background="#400000FF"/>
</Grid>"##;

#[test]
fn diagnostics_resource_mirrors_allocator_counters() {
    noesis_license_from_env();

    let early: Arc<Mutex<Option<NoesisDiagnostics>>> = Arc::new(Mutex::new(None));
    let late: Arc<Mutex<Option<NoesisDiagnostics>>> = Arc::new(Mutex::new(None));

    let mut app = App::new();
    app.add_plugins(
        DefaultPlugins
            .build()
            .disable::<bevy::winit::WinitPlugin>()
            .set(WindowPlugin {
                primary_window: None,
                exit_condition: ExitCondition::DontExit,
                close_when_requested: false,
                ..default()
            }),
    );
    app.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_millis(4)));
    app.add_plugins(NoesisPlugin::default());

    app.add_systems(
        Startup,
        |mut commands: Commands, mut reg: ResMut<XamlRegistry>| {
            reg.insert("diag.xaml".to_string(), Arc::new(XAML.as_bytes().to_vec()));
            commands.spawn((
                Camera2d,
                NoesisCamera,
                NoesisView {
                    xaml_uri: "diag.xaml".to_string(),
                    size: UVec2::new(64, 32),
                    ..default()
                },
            ));
        },
    );

    let early_sys = Arc::clone(&early);
    let late_sys = Arc::clone(&late);
    app.add_systems(
        Update,
        move |mut frame: Local<usize>,
              diag: Res<NoesisDiagnostics>,
              mut exit: MessageWriter<AppExit>| {
            *frame += 1;
            if *frame == EARLY_AT_FRAME {
                *early_sys.lock().unwrap() = Some(*diag);
            }
            if *frame == SAMPLE_AT_FRAME {
                *late_sys.lock().unwrap() = Some(*diag);
            }
            if *frame >= EXIT_AT_FRAME {
                exit.write(AppExit::Success);
            }
        },
    );

    app.run();

    let late = snapshot(&late, "late");
    let early = snapshot(&early, "early");
    eprintln!("--- NoesisDiagnostics early={early:?} late={late:?} ---");

    assert!(
        late.allocated_memory > 0,
        "allocated_memory should be non-zero after a scene builds (default 0); got {}",
        late.allocated_memory,
    );
    assert!(
        late.allocations_count > 0,
        "allocations_count should be non-zero after a scene builds (default 0); got {}",
        late.allocations_count,
    );
    // accum is cumulative-ever, allocated is live; catches a transposed/garbage read.
    assert!(
        late.allocated_memory_accum >= late.allocated_memory,
        "accum ({}) must be >= live allocated ({})",
        late.allocated_memory_accum,
        late.allocated_memory,
    );
    // early non-zero proves refresh runs every frame; init() allocates before any scene.
    assert!(
        early.allocations_count > 0,
        "early allocations_count should be non-zero (engine init allocates); got {}",
        early.allocations_count,
    );
}

fn snapshot(slot: &Arc<Mutex<Option<NoesisDiagnostics>>>, which: &str) -> NoesisDiagnostics {
    slot.lock()
        .unwrap()
        .unwrap_or_else(|| panic!("{which} diagnostics snapshot captured"))
}

fn noesis_license_from_env() {
    if let (Ok(name), Ok(key)) = (
        std::env::var("NOESIS_LICENSE_NAME"),
        std::env::var("NOESIS_LICENSE_KEY"),
    ) {
        noesis_runtime::set_license(&name, &key);
    }
}
