use super::*;

fn pixel_is_dark(image: &Image, x: u32, y: u32) -> bool {
    let idx = ((y * image.width + x) * 4) as usize;
    image.pixels[idx..idx + 3]
        .iter()
        .all(|channel| *channel < 220)
}

fn pixel_is_bright_rgba(image: &image::RgbaImage, x: u32, y: u32) -> bool {
    let pixel = image.get_pixel(x, y).0;
    pixel[0] > 200 && pixel[1] > 200 && pixel[2] > 200 && pixel[3] > 0
}

fn image_pixel_rgba(image: &Image, x: u32, y: u32) -> [u8; 4] {
    let idx = ((y * image.width + x) * 4) as usize;
    [
        image.pixels[idx],
        image.pixels[idx + 1],
        image.pixels[idx + 2],
        image.pixels[idx + 3],
    ]
}

fn count_red_pixels_outside_rect(image: &Image, rect: Rect) -> usize {
    let left = rect.left().floor() as i32;
    let right = rect.right().ceil() as i32;
    let top = rect.top().floor() as i32;
    let bottom = rect.bottom().ceil() as i32;
    let mut count = 0usize;

    for y in 0..image.height as i32 {
        for x in 0..image.width as i32 {
            if x >= left && x < right && y >= top && y < bottom {
                continue;
            }

            let idx = ((y as u32 * image.width + x as u32) * 4) as usize;
            let pixel = &image.pixels[idx..idx + 4];
            if pixel[3] > 0 && pixel[0] > 160 && pixel[1] < 80 && pixel[2] < 80 {
                count += 1;
            }
        }
    }

    count
}

#[test]
fn test_renderer_creation() {
    let theme = Theme::default();
    let renderer = SkiaRenderer::new(800, 600, theme);
    assert!(renderer.is_ok());

    let renderer = renderer.unwrap();
    assert_eq!(renderer.width, 800);
    assert_eq!(renderer.height, 600);
}

#[test]
fn test_set_dpi_scale_sanitizes_invalid_values() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(100, 100, theme).unwrap();

    renderer.set_dpi_scale(2.5);
    assert!((renderer.dpi_scale() - 2.5).abs() < f32::EPSILON);

    renderer.set_dpi_scale(0.0);
    assert!((renderer.dpi_scale() - 1.0).abs() < f32::EPSILON);

    renderer.set_dpi_scale(-3.0);
    assert!((renderer.dpi_scale() - 1.0).abs() < f32::EPSILON);

    renderer.set_dpi_scale(f32::NAN);
    assert!((renderer.dpi_scale() - 1.0).abs() < f32::EPSILON);

    renderer.set_dpi_scale(f32::INFINITY);
    assert!((renderer.dpi_scale() - 1.0).abs() < f32::EPSILON);
}

#[test]
fn test_plot_area_calculation() {
    let area = calculate_plot_area(800, 600, 0.1);
    assert_eq!(area.left(), 80.0);
    assert_eq!(area.top(), 60.0);
    assert_eq!(area.width(), 640.0);
    assert_eq!(area.height(), 480.0);
}

#[test]
fn test_data_to_pixel_mapping() {
    let plot_area = Rect::from_xywh(100.0, 100.0, 600.0, 400.0).unwrap();
    let (px, py) = map_data_to_pixels(
        1.5, 2.5, // data coordinates
        1.0, 2.0, // data x range
        2.0, 3.0, // data y range
        plot_area,
    );

    assert_eq!(px, 400.0); // middle of x range
    assert_eq!(py, 300.0); // middle of y range (flipped)
}

#[test]
fn test_tick_generation() {
    let ticks = generate_ticks(0.0, 10.0, 5);
    assert!(!ticks.is_empty());
    assert!(ticks[0] >= 0.0);
    assert!(ticks.last().unwrap() <= &10.0);

    // Test edge case
    let ticks = generate_ticks(5.0, 5.0, 3);
    assert_eq!(ticks, vec![5.0, 5.0]);
}

#[test]
fn test_compute_colorbar_ticks_formats_log_decades_and_minor_ticks() {
    let ticks = compute_colorbar_ticks(1e-5, 1e3, &crate::axes::AxisScale::Log, true);

    assert_eq!(ticks.major_labels.first().map(String::as_str), Some("10⁻⁵"));
    assert_eq!(ticks.major_labels.last().map(String::as_str), Some("10³"));
    assert!(ticks.minor_values.contains(&2e-5));
    assert!(ticks.minor_values.contains(&900.0));
}

#[test]
fn test_colorbar_layout_metrics_keep_rotated_label_after_tick_labels() {
    let metrics = super::compute_colorbar_layout_metrics(20.0, 12.0, 36.0, Some(14.0));

    assert!((metrics.major_tick_width - 6.0).abs() < 1e-6);
    assert!((metrics.minor_tick_width - 3.6).abs() < 1e-6);
    assert!(metrics.tick_label_x_offset > 20.0);
    assert_eq!(metrics.rotated_label_center_x_offset, Some(78.0));
    assert!((metrics.total_extent - 85.0).abs() < 1e-6);
}

#[test]
fn test_colorbar_major_label_top_centers_label_on_tick() {
    let tick_y = 48.0;
    let label_center_from_top = 9.5;
    let label_top = super::colorbar_major_label_top(tick_y, label_center_from_top);

    assert!(((label_top + label_center_from_top) - tick_y).abs() < f32::EPSILON);
}

#[test]
fn test_colorbar_major_label_anchor_center_uses_base_center_for_log_decades() {
    let anchor_center = super::colorbar_major_label_anchor_center_from_top(
        &crate::axes::AxisScale::Log,
        "10²",
        8.0,
        Some(6.0),
    );

    assert!((anchor_center - 6.0).abs() < f32::EPSILON);
}

#[test]
fn test_colorbar_major_label_anchor_center_keeps_rendered_center_for_non_log_labels() {
    let anchor_center = super::colorbar_major_label_anchor_center_from_top(
        &crate::axes::AxisScale::Linear,
        "6",
        8.0,
        Some(6.0),
    );

    assert!((anchor_center - 8.0).abs() < f32::EPSILON);
}

#[test]
fn test_draw_pixel_aligned_solid_rectangle_blends_transparent_fill() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(6, 6, theme).unwrap();
    renderer
        .pixmap
        .fill(crate::render::Color::new(0, 0, 255).to_tiny_skia_color());

    renderer
        .draw_pixel_aligned_solid_rectangle(
            1.0,
            1.0,
            4.0,
            4.0,
            crate::render::Color::new_rgba(255, 0, 0, 128),
        )
        .expect("transparent pixel-aligned fill should composite successfully");

    let image = renderer.into_image();
    let pixel = image_pixel_rgba(&image, 2, 2);
    assert!(
        pixel[0] > 0,
        "composited fill should retain red contribution"
    );
    assert!(
        pixel[2] > 0,
        "composited fill should retain background contribution instead of overwriting it"
    );
    assert_eq!(pixel[3], 255);
}

#[test]
fn test_draw_pixel_aligned_solid_rectangle_preserves_subpixel_tiles() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(8, 8, theme).unwrap();
    renderer.pixmap.fill(tiny_skia::Color::TRANSPARENT);

    renderer
        .draw_pixel_aligned_solid_rectangle(3.2, 1.0, 0.25, 6.0, crate::render::Color::RED)
        .expect("subpixel-aligned fill should render via composited fallback");

    let image = renderer.into_image();
    assert!(
        image.pixels.chunks_exact(4).any(|pixel| pixel[3] > 0),
        "thin pixel-aligned tiles should still contribute visible coverage"
    );
}

#[test]
fn test_draw_axes_with_config_draws_top_and_right_ticks() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(120, 100, theme).unwrap();
    let plot_area = Rect::from_xywh(20.0, 20.0, 80.0, 60.0).unwrap();

    renderer
        .draw_axes_with_config(
            plot_area,
            &[60.0],
            &[50.0],
            &[],
            &[],
            &TickDirection::Inside,
            &TickSides::all(),
            Color::BLACK,
            1.0,
        )
        .unwrap();

    let image = renderer.into_image();
    assert!(pixel_is_dark(&image, 60, 20));
    assert!(pixel_is_dark(&image, 100, 50));
    assert!(pixel_is_dark(&image, 60, 24));
    assert!(pixel_is_dark(&image, 96, 50));
}

#[test]
fn test_draw_axes_with_config_respects_bottom_left_ticks() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(120, 100, theme).unwrap();
    let plot_area = Rect::from_xywh(20.0, 20.0, 80.0, 60.0).unwrap();

    renderer
        .draw_axes_with_config(
            plot_area,
            &[60.0],
            &[50.0],
            &[],
            &[],
            &TickDirection::Inside,
            &TickSides::bottom_left(),
            Color::BLACK,
            1.0,
        )
        .unwrap();

    let image = renderer.into_image();
    assert!(pixel_is_dark(&image, 60, 20));
    assert!(pixel_is_dark(&image, 100, 50));
    assert!(!pixel_is_dark(&image, 60, 24));
    assert!(!pixel_is_dark(&image, 96, 50));
    assert!(pixel_is_dark(&image, 60, 76));
    assert!(pixel_is_dark(&image, 24, 50));
}

#[test]
fn test_draw_axis_labels_at_handles_collapsed_ranges() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(120, 100, theme).unwrap();
    let plot_area = LayoutRect {
        left: 20.0,
        top: 20.0,
        right: 100.0,
        bottom: 80.0,
    };

    renderer
        .draw_axis_labels_at(
            &plot_area,
            1.0,
            1.0,
            2.0,
            2.0,
            &[1.0],
            &[2.0],
            88.0,
            18.0,
            10.0,
            Color::BLACK,
            100.0,
            true,
            false,
        )
        .expect("collapsed ranges should use centered label placement");
}

#[test]
fn test_draw_polyline_clipped_keeps_pixels_inside_clip_rect() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(120, 120, theme).unwrap();
    let clip_rect = Rect::from_xywh(20.0, 20.0, 80.0, 80.0).unwrap();

    renderer
        .draw_polyline_clipped(
            &[(20.0, 20.0), (100.0, 100.0)],
            Color::new(220, 20, 20),
            18.0,
            LineStyle::Solid,
            (
                clip_rect.x(),
                clip_rect.y(),
                clip_rect.width(),
                clip_rect.height(),
            ),
        )
        .unwrap();

    let image = renderer.into_image();
    assert_eq!(count_red_pixels_outside_rect(&image, clip_rect), 0);
}

#[test]
fn test_draw_datashader_image_scales_into_plot_area() {
    let theme = Theme::dark();
    let mut renderer = SkiaRenderer::new(80, 60, theme).unwrap();
    renderer.clear();

    let image = crate::data::DataShaderImage::new(1, 1, vec![255, 255, 255, 255]);
    let plot_area = Rect::from_xywh(10.0, 15.0, 30.0, 20.0).unwrap();

    renderer.draw_datashader_image(&image, plot_area).unwrap();

    let png = renderer.encode_png_bytes().unwrap();
    let rendered = image::load_from_memory(&png).unwrap().to_rgba8();
    assert!(pixel_is_bright_rgba(&rendered, 15, 20));
    assert!(pixel_is_bright_rgba(&rendered, 35, 30));
    assert!(!pixel_is_bright_rgba(&rendered, 5, 5));
    assert!(!pixel_is_bright_rgba(&rendered, 50, 45));
}

#[cfg(feature = "typst-math")]
#[test]
fn test_typst_raster_uses_native_1x_scale() {
    let theme = Theme::default();
    let mut renderer = SkiaRenderer::new(400, 300, theme).unwrap();
    renderer.set_dpi_scale(1.0);
    renderer.set_text_engine_mode(TextEngineMode::Typst);

    let rendered_native = typst_text::render_raster(
        "scale-check",
        12.0,
        Color::BLACK,
        0.0,
        "typst native scale test",
    )
    .unwrap();
    let rendered_second = typst_text::render_raster(
        "scale-check",
        12.0,
        Color::BLACK,
        0.0,
        "typst native scale test",
    )
    .unwrap();

    assert_eq!(
        rendered_native.pixmap.width(),
        rendered_second.pixmap.width()
    );
    assert_eq!(
        rendered_native.pixmap.height(),
        rendered_second.pixmap.height()
    );
    assert!(
        (rendered_native.pixmap.width() as f32 - rendered_native.width).abs() <= 1.0,
        "native raster width should align with logical width: pixel={} logical={}",
        rendered_native.pixmap.width(),
        rendered_native.width
    );
    assert!(
        (rendered_native.pixmap.height() as f32 - rendered_native.height).abs() <= 1.0,
        "native raster height should align with logical height: pixel={} logical={}",
        rendered_native.pixmap.height(),
        rendered_native.height
    );
}

#[test]
fn test_renderer_dimensions() {
    let theme = Theme::default();
    let renderer = SkiaRenderer::new(800, 600, theme).unwrap();

    assert_eq!(renderer.width(), 800);
    assert_eq!(renderer.height(), 600);
}

#[test]
fn test_draw_subplot() {
    use crate::core::plot::Image;

    let theme = Theme::default();
    let mut main_renderer = SkiaRenderer::new(800, 600, theme.clone()).unwrap();
    let subplot_renderer = SkiaRenderer::new(200, 150, theme).unwrap();

    // Convert subplot to image
    let subplot_image = subplot_renderer.into_image();

    // Should draw subplot without error
    let result = main_renderer.draw_subplot(subplot_image, 10, 20);
    assert!(result.is_ok());
}

#[test]
fn test_draw_subplot_bounds_checking() {
    use crate::core::plot::Image;

    let theme = Theme::default();
    let mut main_renderer = SkiaRenderer::new(400, 300, theme.clone()).unwrap();
    let subplot_renderer = SkiaRenderer::new(200, 150, theme).unwrap();

    let subplot_image = subplot_renderer.into_image();

    // Should handle valid positions
    assert!(
        main_renderer
            .draw_subplot(subplot_image.clone(), 0, 0)
            .is_ok()
    );
    assert!(
        main_renderer
            .draw_subplot(subplot_image.clone(), 100, 50)
            .is_ok()
    );

    // Should handle edge positions
    assert!(main_renderer.draw_subplot(subplot_image, 200, 150).is_ok());
}

#[test]
fn test_to_image_conversion() {
    let theme = Theme::default();
    let renderer = SkiaRenderer::new(400, 300, theme).unwrap();

    let image = renderer.into_image();

    assert_eq!(image.width, 400);
    assert_eq!(image.height, 300);
    assert_eq!(image.pixels.len(), 400 * 300 * 4); // RGBA pixels
}
