use kuva::backend::svg::SvgBackend;
use kuva::plot::line::LinePlot;
use kuva::plot::scatter::ScatterPlot;
use kuva::plot::{PieLabelPosition, PiePlot};
use kuva::render::layout::{ComputedLayout, Layout};
use kuva::render::plots::Plot;
use kuva::render::render::{render_multiple, render_twin_y};

/// Extract the `x` attribute value from the SVG `<text>` element whose content
/// matches `text`. Finds `>text<`, walks back to the nearest `x="..."`.
fn extract_text_x(svg: &str, text: &str) -> Option<f64> {
    let needle = format!(">{}<", text);
    let pos = svg.find(&needle)?;
    let before = &svg[..pos];
    let x_attr = before.rfind("x=\"")?;
    let after_quote = &before[x_attr + 3..];
    let end = after_quote.find('"')?;
    after_quote[..end].parse::<f64>().ok()
}

/// Extract the `y` attribute value from the SVG `<text>` element whose content
/// matches `text`. Finds `>text<`, walks back to the nearest `y="..."`.
fn extract_text_y(svg: &str, text: &str) -> Option<f64> {
    let needle = format!(">{}<", text);
    let pos = svg.find(&needle)?;
    let before = &svg[..pos];
    let y_attr = before.rfind("y=\"")?;
    let after_quote = &before[y_attr + 3..];
    let end = after_quote.find('"')?;
    after_quote[..end].parse::<f64>().ok()
}

#[test]
fn test_title_centred_with_legend() {
    let data = vec![(1.0f64, 2.0f64), (3.0, 4.0), (5.0, 6.0)];
    let plot = ScatterPlot::new().with_data(data).with_legend("Group A");
    let plots = vec![Plot::Scatter(plot)];

    let layout = Layout::auto_from_plots(&plots)
        .with_title("MyTitle")
        .with_x_label("MyLabel");

    let computed = ComputedLayout::from_layout(&layout);
    // Title centers on full canvas width; x-label centers on plot area.
    let expected_title_x = computed.width / 2.0;
    let expected_label_x = computed.margin_left + computed.plot_width() / 2.0;

    let scene = render_multiple(plots, layout);
    let svg = SvgBackend.render_scene(&scene);
    std::fs::write("test_outputs/label_centering_legend.svg", &svg).unwrap();

    let title_x = extract_text_x(&svg, "MyTitle").expect("title element not found in SVG");
    let label_x = extract_text_x(&svg, "MyLabel").expect("x-label element not found in SVG");

    assert!(
        (title_x - expected_title_x).abs() < 1.0,
        "title x={title_x:.1} should equal canvas_width/2={expected_title_x:.1}"
    );
    assert!(
        (label_x - expected_label_x).abs() < 1.0,
        "x-label x={label_x:.1} should equal margin_left+plot_width/2={expected_label_x:.1}"
    );
}

#[test]
fn test_title_centred_twin_y() {
    let primary = vec![Plot::Line(
        LinePlot::new()
            .with_data(vec![(1.0f64, 5.0f64), (2.0, 8.0), (3.0, 14.0)])
            .with_legend("Temp"),
    )];
    let secondary = vec![Plot::Line(
        LinePlot::new()
            .with_data(vec![(1.0f64, 80.0f64), (2.0, 60.0), (3.0, 45.0)])
            .with_legend("Rain"),
    )];

    let layout = Layout::auto_from_twin_y_plots(&primary, &secondary)
        .with_title("TwinTitle")
        .with_x_label("X");

    let computed = ComputedLayout::from_layout(&layout);
    // Title centers on full canvas width; x-label centers on plot area.
    let expected_title_x = computed.width / 2.0;
    let expected_label_x = computed.margin_left + computed.plot_width() / 2.0;

    let scene = render_twin_y(primary, secondary, layout);
    let svg = SvgBackend.render_scene(&scene);
    std::fs::write("test_outputs/label_centering_twin_y.svg", &svg).unwrap();

    let title_x = extract_text_x(&svg, "TwinTitle").expect("title element not found in SVG");
    let label_x = extract_text_x(&svg, "X").expect("x-label element not found in SVG");

    assert!(
        (title_x - expected_title_x).abs() < 1.0,
        "twin-y title x={title_x:.1} should equal canvas_width/2={expected_title_x:.1}"
    );
    assert!(
        (label_x - expected_label_x).abs() < 1.0,
        "twin-y x-label x={label_x:.1} should equal margin_left+plot_width/2={expected_label_x:.1}"
    );
}

#[test]
fn test_title_centred_pie_outside_labels() {
    let pie = PiePlot::new()
        .with_slice("Alpha", 30.0, "steelblue")
        .with_slice("Beta", 25.0, "tomato")
        .with_slice("Gamma", 20.0, "seagreen")
        .with_slice("Delta", 15.0, "orange")
        .with_slice("Epsilon", 10.0, "purple")
        .with_label_position(PieLabelPosition::Outside);

    let plots = vec![Plot::Pie(pie.clone())];
    let layout = Layout::auto_from_plots(&plots).with_title("PieTitle");

    // Pre-compute margins from the layout (these are stable across the widening).
    let computed = ComputedLayout::from_layout(&layout);
    let margin_left = computed.margin_left;
    let margin_right = computed.margin_right;

    // Canvas widening happens inside render_multiple; compute expected x after that
    // by reading the final canvas width from the <svg> width attribute.
    let scene = render_multiple(plots, layout);
    let svg = SvgBackend.render_scene(&scene);
    std::fs::write("test_outputs/label_centering_pie_outside.svg", &svg).unwrap();

    // Extract the final canvas width from the SVG header: width="NNN"
    let canvas_width: f64 = {
        let w_pos = svg.find("width=\"").expect("width attr in SVG");
        let after = &svg[w_pos + 7..];
        let end = after.find('"').unwrap();
        after[..end].parse().unwrap()
    };

    // Title centers on full canvas width regardless of legend/pie-widening margins.
    let expected_x = canvas_width / 2.0;
    let _ = (margin_left, margin_right); // retained for context

    let title_x = extract_text_x(&svg, "PieTitle").expect("title element not found in SVG");

    assert!(
        (title_x - expected_x).abs() < 1.0,
        "pie title x={title_x:.1} should equal canvas_width/2={expected_x:.1} \
         (canvas={canvas_width:.1})"
    );
}

// ── Label offset API tests ────────────────────────────────────────────────────

fn make_scatter_plots() -> Vec<Plot> {
    let data = vec![(1.0f64, 2.0f64), (3.0, 4.0), (5.0, 6.0)];
    vec![Plot::Scatter(ScatterPlot::new().with_data(data))]
}

fn make_twin_y_plots() -> (Vec<Plot>, Vec<Plot>) {
    let primary = vec![Plot::Line(LinePlot::new().with_data(vec![
        (1.0f64, 5.0f64),
        (2.0, 8.0),
        (3.0, 14.0),
    ]))];
    let secondary = vec![Plot::Line(LinePlot::new().with_data(vec![
        (1.0f64, 80.0f64),
        (2.0, 60.0),
        (3.0, 45.0),
    ]))];
    (primary, secondary)
}

#[test]
fn test_x_label_offset() {
    let (dx, dy) = (20.0_f64, -5.0_f64);

    // Baseline — no offset
    let plots = make_scatter_plots();
    let layout = Layout::auto_from_plots(&plots).with_x_label("XLbl");
    let svg_base = SvgBackend.render_scene(&render_multiple(plots, layout));

    // With offset
    let plots = make_scatter_plots();
    let layout = Layout::auto_from_plots(&plots)
        .with_x_label("XLbl")
        .with_x_label_offset(dx, dy);
    let svg_off = SvgBackend.render_scene(&render_multiple(plots, layout));

    std::fs::write("test_outputs/x_label_offset.svg", &svg_off).unwrap();

    let base_x = extract_text_x(&svg_base, "XLbl").expect("base x-label not found");
    let base_y = extract_text_y(&svg_base, "XLbl").expect("base y not found");
    let off_x = extract_text_x(&svg_off, "XLbl").expect("offset x-label not found");
    let off_y = extract_text_y(&svg_off, "XLbl").expect("offset y not found");

    assert!(
        (off_x - base_x - dx).abs() < 0.5,
        "x-label x: expected shift {dx}, got {:.1} → {:.1}",
        base_x,
        off_x
    );
    assert!(
        (off_y - base_y - dy).abs() < 0.5,
        "x-label y: expected shift {dy}, got {:.1} → {:.1}",
        base_y,
        off_y
    );
}

#[test]
fn test_y_label_offset() {
    let (dx, dy) = (8.0_f64, 15.0_f64);

    // Baseline
    let plots = make_scatter_plots();
    let layout = Layout::auto_from_plots(&plots).with_y_label("YLbl");
    let svg_base = SvgBackend.render_scene(&render_multiple(plots, layout));

    // With offset
    let plots = make_scatter_plots();
    let layout = Layout::auto_from_plots(&plots)
        .with_y_label("YLbl")
        .with_y_label_offset(dx, dy);
    let svg_off = SvgBackend.render_scene(&render_multiple(plots, layout));

    std::fs::write("test_outputs/y_label_offset.svg", &svg_off).unwrap();

    let base_x = extract_text_x(&svg_base, "YLbl").expect("base y-label x not found");
    let base_y = extract_text_y(&svg_base, "YLbl").expect("base y-label y not found");
    let off_x = extract_text_x(&svg_off, "YLbl").expect("offset y-label x not found");
    let off_y = extract_text_y(&svg_off, "YLbl").expect("offset y-label y not found");

    assert!(
        (off_x - base_x - dx).abs() < 0.5,
        "y-label x: expected shift {dx}, got {:.1} → {:.1}",
        base_x,
        off_x
    );
    assert!(
        (off_y - base_y - dy).abs() < 0.5,
        "y-label y: expected shift {dy}, got {:.1} → {:.1}",
        base_y,
        off_y
    );
}

#[test]
fn test_y2_label_offset() {
    let (dx, dy) = (-10.0_f64, 20.0_f64);

    // Baseline
    let (primary, secondary) = make_twin_y_plots();
    let layout = Layout::auto_from_twin_y_plots(&primary, &secondary).with_y2_label("Y2Lbl");
    let svg_base = SvgBackend.render_scene(&render_twin_y(primary, secondary, layout));

    // With offset
    let (primary, secondary) = make_twin_y_plots();
    let layout = Layout::auto_from_twin_y_plots(&primary, &secondary)
        .with_y2_label("Y2Lbl")
        .with_y2_label_offset(dx, dy);
    let svg_off = SvgBackend.render_scene(&render_twin_y(primary, secondary, layout));

    std::fs::write("test_outputs/y2_label_offset.svg", &svg_off).unwrap();

    let base_x = extract_text_x(&svg_base, "Y2Lbl").expect("base y2-label x not found");
    let base_y = extract_text_y(&svg_base, "Y2Lbl").expect("base y2-label y not found");
    let off_x = extract_text_x(&svg_off, "Y2Lbl").expect("offset y2-label x not found");
    let off_y = extract_text_y(&svg_off, "Y2Lbl").expect("offset y2-label y not found");

    assert!(
        (off_x - base_x - dx).abs() < 0.5,
        "y2-label x: expected shift {dx}, got {:.1} → {:.1}",
        base_x,
        off_x
    );
    assert!(
        (off_y - base_y - dy).abs() < 0.5,
        "y2-label y: expected shift {dy}, got {:.1} → {:.1}",
        base_y,
        off_y
    );
}
