// Defensive coverage for the image-upload boundary. The GLES path requires a
// live GL context (out of scope for these CPU-only tests) so the assertions
// below exercise the software backend, which shares the same length-vs-
// dimensions check before delegating to tiny-skia. Both paths:
//
// * refuse the upload when `data.len() != width * height * 4`,
// * refuse zero-area uploads (`width == 0` or `height == 0`),
// * never panic — they log to stderr and return without drawing.

use std::sync::Arc;

use ltk::core::{ RenderOptions, UiSurface };
use ltk::{ column, img_widget, Color, Element };

fn render_image( rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> UiSurface<()>
{
	let mut surface = UiSurface::<()>::new( 64, 64 );
	let view: Element<()> = column()
		.padding( 0.0 )
		.push( img_widget( rgba, w, h ) )
		.into();
	let _ = surface.render(
		&view,
		RenderOptions::full_canvas( 64, 64 ).background( Color::BLACK ),
	);
	surface
}

#[ test ]
fn declared_size_matching_buffer_renders_without_panic()
{
	// Sanity baseline: a 4×4 image with the expected 64-byte buffer must
	// render through the path that the validation tests below exercise.
	let bytes = Arc::new( vec![ 0xFFu8; 4 * 4 * 4 ] );
	let _ = render_image( bytes, 4, 4 );
}

#[ test ]
fn shorter_buffer_is_rejected_without_panic()
{
	// 10×10 declared but only 16 bytes provided. Without the boundary
	// check tiny-skia would walk past the slice end inside its premul
	// loop. With the check the renderer logs once and skips the draw.
	let bytes = Arc::new( vec![ 0u8; 16 ] );
	let _ = render_image( bytes, 10, 10 );
}

#[ test ]
fn longer_buffer_is_rejected_without_panic()
{
	// 2×2 declared, 256 bytes provided. The buffer is larger than the
	// driver would read, but the size mismatch still fails the strict
	// equality and the draw is refused.
	let bytes = Arc::new( vec![ 0u8; 256 ] );
	let _ = render_image( bytes, 2, 2 );
}

#[ test ]
fn zero_width_image_does_not_blit()
{
	// IntSize::from_wh rejects zero dimensions on tiny-skia, but the
	// boundary check fires earlier for a uniform error path on both
	// backends.
	let bytes = Arc::new( vec![] );
	let _ = render_image( bytes, 0, 10 );
}

#[ test ]
fn zero_height_image_does_not_blit()
{
	let bytes = Arc::new( vec![] );
	let _ = render_image( bytes, 10, 0 );
}

#[ test ]
fn non_multiple_of_four_buffer_is_rejected()
{
	// 1×1 should be 4 bytes; 3 bytes is shorter and not a multiple of 4 —
	// every byte counts.
	let bytes = Arc::new( vec![ 0xFF, 0xFF, 0xFF ] );
	let _ = render_image( bytes, 1, 1 );
}

#[ test ]
fn dimensions_overflow_usize_are_rejected_without_panic()
{
	// `u32::MAX × u32::MAX × 4` overflows. The boundary check uses
	// `saturating_mul` (or i64-promotion in the GLES path) so the
	// expected length never wraps to a small number that happens to
	// match a real buffer length. The provided buffer is small, so the
	// equality fails and the draw is refused.
	let bytes = Arc::new( vec![ 0u8; 16 ] );
	let _ = render_image( bytes, u32::MAX, u32::MAX );
}

#[ test ]
fn renderer_recovers_after_a_rejected_upload()
{
	// A frame with a malformed image must not poison the surface. The
	// next render with a valid view succeeds.
	let bad   = Arc::new( vec![ 0u8; 5 ] );
	let mut s = render_image( bad, 4, 4 );

	let view: Element<()> = column().padding( 0.0 ).into();
	let out = s.render(
		&view,
		RenderOptions::full_canvas( 64, 64 ).background( Color::rgb( 0.0, 1.0, 0.0 ) ),
	);
	// Tree shape change → full redraw, but the important check is that
	// render() returned at all.
	let _ = out;
}
