#![allow(clippy::unwrap_used)]

use crate::constants::{DEFAULT_ROW_HEIGHT, LAST_COLUMN, LAST_ROW};
use crate::model::Model;
use crate::test::util::new_empty_model;
use crate::types::Col;

#[test]
fn test_insert_columns() {
    let mut model = new_empty_model();
    // We populate cells A1 to C1
    model._set("A1", "1");
    model._set("B1", "2");
    model._set("C1", "=B1*2");

    model._set("F1", "=B1");

    model._set("L11", "300");
    model._set("M11", "=L11*5");

    model.evaluate();
    assert_eq!(model._get_text("C1"), *"4");

    // Let's insert 5 columns in column F (6)
    let r = model.insert_columns(0, 6, 5);
    assert!(r.is_ok());
    model.evaluate();

    // Check F1 is now empty
    assert!(model.is_empty_cell(0, 1, 6).unwrap());

    // The old F1 is K1
    assert_eq!(model._get_formula("K1"), *"=B1");

    // L11 and M11 are Q11 and R11
    assert_eq!(model._get_text("Q11"), *"300");
    assert_eq!(model._get_formula("R11"), *"=Q11*5");

    assert_eq!(model._get_formula("C1"), "=B1*2");
    assert_eq!(model._get_text("A1"), "1");

    // inserting a negative number of columns fails:
    let r = model.insert_columns(0, 6, -5);
    assert!(r.is_err());
    let r = model.insert_columns(0, 6, -5);
    assert!(r.is_err());

    // If you have data at the very ebd it fails
    model._set("XFC12", "300");
    let r = model.insert_columns(0, 6, 5);
    assert!(r.is_err());
}

#[test]
fn test_insert_rows() {
    let mut model = new_empty_model();

    model._set("C4", "3");
    model._set("C5", "7");
    model._set("C6", "=C5");

    model._set("H11", "=C4");

    model._set("R10", "=C6");

    model.evaluate();

    // Let's insert 5 rows in row 6
    let r = model.insert_rows(0, 6, 5);
    assert!(r.is_ok());
    model.evaluate();

    // Check C6 is now empty
    assert!(model.is_empty_cell(0, 6, 3).unwrap());

    // Old C6 is now C11
    assert_eq!(model._get_formula("C11"), *"=C5");
    assert_eq!(model._get_formula("H16"), *"=C4");

    assert_eq!(model._get_formula("R15"), *"=C11");
    assert_eq!(model._get_text("C4"), *"3");
    assert_eq!(model._get_text("C5"), *"7");
}

#[test]
fn test_insert_rows_styles() {
    let mut model = new_empty_model();

    assert!(
        (DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
            < f64::EPSILON
    );
    // sets height 42 in row 10
    model
        .workbook
        .worksheet_mut(0)
        .unwrap()
        .set_row_height(10, 42.0)
        .unwrap();
    assert!(
        (42.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
    );

    // Let's insert 5 rows in row 3
    let r = model.insert_rows(0, 3, 5);
    assert!(r.is_ok());

    // Row 10 has the default height
    assert!(
        (DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
            < f64::EPSILON
    );

    // Row 10 is now row 15
    assert!(
        (42.0 - model.workbook.worksheet(0).unwrap().row_height(15).unwrap()).abs() < f64::EPSILON
    );
}

#[test]
fn test_delete_rows_styles() {
    let mut model = new_empty_model();

    assert!(
        (DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
            < f64::EPSILON
    );
    // sets height 42 in row 10
    model
        .workbook
        .worksheet_mut(0)
        .unwrap()
        .set_row_height(10, 42.0)
        .unwrap();
    assert!(
        (42.0 - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs() < f64::EPSILON
    );

    // Let's delete 5 rows in row 3 (3-8)
    let r = model.delete_rows(0, 3, 5);
    assert!(r.is_ok());

    // Row 10 has the default height
    assert!(
        (DEFAULT_ROW_HEIGHT - model.workbook.worksheet(0).unwrap().row_height(10).unwrap()).abs()
            < f64::EPSILON
    );

    // Row 10 is now row 5
    assert!(
        (42.0 - model.workbook.worksheet(0).unwrap().row_height(5).unwrap()).abs() < f64::EPSILON
    );
}

#[test]
fn test_delete_columns() {
    let mut model = new_empty_model();

    model._set("C4", "3");
    model._set("D4", "7");
    model._set("E4", "=D4");
    model._set("F4", "=C4");

    model._set("H11", "=D4");

    model._set("R10", "=C6");

    model._set("M5", "300");
    model._set("N5", "=M5*6");

    model._set("A1", "=SUM(M5:N5)");
    model._set("A2", "=SUM(C4:M4)");
    model._set("A3", "=SUM(E4:M4)");

    model.evaluate();

    // We delete columns D and E
    let r = model.delete_columns(0, 4, 2);
    assert!(r.is_ok());
    model.evaluate();

    // Old H11 will be F11 and contain =#REF!
    assert_eq!(model._get_formula("F11"), *"=#REF!");

    // Old F4 will be D4 now
    assert_eq!(model._get_formula("D4"), *"=C4");

    // Old N5 will be L5
    assert_eq!(model._get_formula("L5"), *"=K5*6");

    // Range in A1 is displaced correctly
    assert_eq!(model._get_formula("A1"), *"=SUM(K5:L5)");

    // Note that range in A2 would contain some of the deleted cells
    // A long as the borders of the range are not included that's ok.
    assert_eq!(model._get_formula("A2"), *"=SUM(C4:K4)");

    // FIXME: In Excel this would be (lower limit won't change)
    // assert_eq!(model._get_formula("A3"), *"=SUM(E4:K4)");
    assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
}

#[test]
fn test_delete_column_width() {
    let mut model = new_empty_model();
    let (sheet, column) = (0, 5);
    let normal_width = model.get_column_width(sheet, column).unwrap();
    // Set the width of one column to 5 times the normal width
    assert!(model
        .set_column_width(sheet, column, normal_width * 5.0)
        .is_ok());

    // delete it
    assert!(model.delete_columns(sheet, column, 1).is_ok());

    // all the columns around have the expected width
    assert_eq!(
        model.get_column_width(sheet, column - 1).unwrap(),
        normal_width
    );
    assert_eq!(model.get_column_width(sheet, column).unwrap(), normal_width);
    assert_eq!(
        model.get_column_width(sheet, column + 1).unwrap(),
        normal_width
    );
}

#[test]
// We set the style of columns 4 to 7 and delete column 4
// We check that columns 4 to 6 have the new style
fn test_delete_first_column_width() {
    let mut model = new_empty_model();
    model.workbook.worksheets[0].cols = vec![Col {
        min: 4,
        max: 7,
        width: 300.0,
        custom_width: true,
        style: None,
    }];
    let (sheet, column) = (0, 4);
    assert!(model.delete_columns(sheet, column, 1).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 1);
    assert_eq!(
        cols[0],
        Col {
            min: 4,
            max: 6,
            width: 300.0,
            custom_width: true,
            style: None
        }
    );
}

#[test]
// Delete the last column in the range
fn test_delete_last_column_width() {
    let mut model = new_empty_model();
    model.workbook.worksheets[0].cols = vec![Col {
        min: 4,
        max: 7,
        width: 300.0,
        custom_width: true,
        style: None,
    }];
    let (sheet, column) = (0, 7);
    assert!(model.delete_columns(sheet, column, 1).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 1);
    assert_eq!(
        cols[0],
        Col {
            min: 4,
            max: 6,
            width: 300.0,
            custom_width: true,
            style: None
        }
    );
}

#[test]
// Deletes columns at the end
fn test_delete_last_few_columns_width() {
    let mut model = new_empty_model();
    model.workbook.worksheets[0].cols = vec![Col {
        min: 4,
        max: 17,
        width: 300.0,
        custom_width: true,
        style: None,
    }];
    let (sheet, column) = (0, 13);
    assert!(model.delete_columns(sheet, column, 10).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 1);
    assert_eq!(
        cols[0],
        Col {
            min: 4,
            max: 12,
            width: 300.0,
            custom_width: true,
            style: None
        }
    );
}

#[test]
fn test_delete_columns_non_overlapping_left() {
    let mut model = new_empty_model();
    model.workbook.worksheets[0].cols = vec![Col {
        min: 10,
        max: 17,
        width: 300.0,
        custom_width: true,
        style: None,
    }];
    let (sheet, column) = (0, 3);
    assert!(model.delete_columns(sheet, column, 4).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 1);
    assert_eq!(
        cols[0],
        Col {
            min: 6,
            max: 13,
            width: 300.0,
            custom_width: true,
            style: None
        }
    );
}

#[test]
fn test_delete_columns_overlapping_left() {
    let mut model = new_empty_model();
    model.workbook.worksheets[0].cols = vec![Col {
        min: 10,
        max: 20,
        width: 300.0,
        custom_width: true,
        style: None,
    }];
    let (sheet, column) = (0, 8);
    assert!(model.delete_columns(sheet, column, 4).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 1);
    assert_eq!(
        cols[0],
        Col {
            min: 8,
            max: 16,
            width: 300.0,
            custom_width: true,
            style: None
        }
    );
}

#[test]
fn test_delete_columns_non_overlapping_right() {
    let mut model = new_empty_model();
    model.workbook.worksheets[0].cols = vec![Col {
        min: 10,
        max: 17,
        width: 300.0,
        custom_width: true,
        style: None,
    }];
    let (sheet, column) = (0, 23);
    assert!(model.delete_columns(sheet, column, 4).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 1);
    assert_eq!(
        cols[0],
        Col {
            min: 10,
            max: 17,
            width: 300.0,
            custom_width: true,
            style: None
        }
    );
}

#[test]
// deletes some columns in the middle of the range
fn test_delete_middle_column_width() {
    let mut model = new_empty_model();
    // styled columns [4, 17]
    model.workbook.worksheets[0].cols = vec![Col {
        min: 4,
        max: 17,
        width: 300.0,
        custom_width: true,
        style: None,
    }];

    // deletes columns 10, 11, 12
    let (sheet, column) = (0, 10);
    assert!(model.delete_columns(sheet, column, 3).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 1);
    assert_eq!(
        cols[0],
        Col {
            min: 4,
            max: 14,
            width: 300.0,
            custom_width: true,
            style: None
        }
    );
}

#[test]
// the range is inside the deleted columns
fn delete_range_in_columns() {
    let mut model = new_empty_model();
    // styled columns [6, 10]
    model.workbook.worksheets[0].cols = vec![Col {
        min: 6,
        max: 10,
        width: 300.0,
        custom_width: true,
        style: None,
    }];

    // deletes columns [4, 17]
    let (sheet, column) = (0, 4);
    assert!(model.delete_columns(sheet, column, 8).is_ok());
    let cols = &model.workbook.worksheets[0].cols;
    assert_eq!(cols.len(), 0);
}

#[test]
fn test_delete_columns_error() {
    let mut model = new_empty_model();
    let (sheet, column) = (0, 5);
    assert!(model.delete_columns(sheet, column, -1).is_err());
    assert!(model.delete_columns(sheet, column, 0).is_err());
    assert!(model.delete_columns(sheet, column, 1).is_ok());
}

#[test]
fn test_delete_rows() {
    let mut model = new_empty_model();

    model._set("C4", "4");
    model._set("C5", "5");
    model._set("C6", "6");
    model._set("C7", "=C6*2");

    model._set("C72", "=C1*3");

    model.evaluate();

    // We delete rows 5, 6
    let r = model.delete_rows(0, 5, 2);
    assert!(r.is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("C5"), *"=#REF!*2");
    assert_eq!(model._get_formula("C70"), *"=C1*3");
}

// E	F	G	H	I	J	K
// 			3	1	1	2
// 			4	2	5	8
// 			-2	3	6	7
fn populate_table(model: &mut Model) {
    model._set("G1", "3");
    model._set("H1", "1");
    model._set("I1", "1");
    model._set("J1", "2");

    model._set("G2", "4");
    model._set("H2", "2");
    model._set("I2", "5");
    model._set("J2", "8");

    model._set("G3", "-2");
    model._set("H3", "3");
    model._set("I3", "6");
    model._set("J3", "7");
}

#[test]
fn test_move_column_right() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    model._set("E3", "=G3");
    model._set("E4", "=H3");
    model._set("E5", "=SUM(G3:J7)");
    model._set("E6", "=SUM(G3:G7)");
    model._set("E7", "=SUM(H3:H7)");
    model.evaluate();

    // Wee swap column G with column H
    let result = model.move_column_action(0, 7, 1);
    assert!(result.is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("E3"), "=H3");
    assert_eq!(model._get_formula("E4"), "=G3");
    assert_eq!(model._get_formula("E5"), "=SUM(H3:J7)");
    assert_eq!(model._get_formula("E6"), "=SUM(H3:H7)");
    assert_eq!(model._get_formula("E7"), "=SUM(G3:G7)");

    // Data moved as well
    assert_eq!(model._get_text("G1"), "1");
    assert_eq!(model._get_text("H1"), "3");
}

#[test]
fn tets_move_column_error() {
    let mut model = new_empty_model();
    model.evaluate();

    let result = model.move_column_action(0, 7, -10);
    assert!(result.is_err());

    let result = model.move_column_action(0, -7, 20);
    assert!(result.is_err());

    let result = model.move_column_action(0, LAST_COLUMN, 1);
    assert!(result.is_err());

    let result = model.move_column_action(0, LAST_COLUMN + 1, -10);
    assert!(result.is_err());

    // This works
    let result = model.move_column_action(0, LAST_COLUMN, -1);
    assert!(result.is_ok());
}

#[test]
fn test_move_row_down() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    // Formulas referencing rows 3 and 4
    model._set("E3", "=G3");
    model._set("E4", "=G4");
    model._set("E5", "=SUM(G3:J3)");
    model._set("E6", "=SUM(G3:G3)");
    model._set("E7", "=SUM(G4:G4)");
    model.evaluate();

    // Move row 3 down by one position
    let result = model.move_row_action(0, 3, 1);
    assert!(result.is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("E3"), "=G3");
    assert_eq!(model._get_formula("E4"), "=G4");
    assert_eq!(model._get_formula("E5"), "=SUM(G4:J4)");
    assert_eq!(model._get_formula("E6"), "=SUM(G4:G4)");
    assert_eq!(model._get_formula("E7"), "=SUM(G3:G3)");

    // Data moved as well
    assert_eq!(model._get_text("G4"), "-2");
    assert_eq!(model._get_text("G3"), "");
}

#[test]
fn test_move_row_up() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    // Formulas referencing rows 4 and 5
    model._set("E4", "=G4");
    model._set("E5", "=G5");
    model._set("E6", "=SUM(G4:J4)");
    model._set("E7", "=SUM(G4:G4)");
    model._set("E8", "=SUM(G5:G5)");
    model.evaluate();

    // Move row 5 up by one position
    let result = model.move_row_action(0, 5, -1);
    assert!(result.is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("E4"), "=G4");
    assert_eq!(model._get_formula("E5"), "=G5");
    assert_eq!(model._get_formula("E6"), "=SUM(G5:J5)");
    assert_eq!(model._get_formula("E7"), "=SUM(G5:G5)");
    assert_eq!(model._get_formula("E8"), "=SUM(G4:G4)");

    // Data moved as well
    assert_eq!(model._get_text("G4"), "");
    assert_eq!(model._get_text("G5"), "");
}

#[test]
fn test_move_row_error() {
    let mut model = new_empty_model();
    model.evaluate();

    let result = model.move_row_action(0, 7, -10);
    assert!(result.is_err());

    let result = model.move_row_action(0, -7, 20);
    assert!(result.is_err());

    let result = model.move_row_action(0, LAST_ROW, 1);
    assert!(result.is_err());

    let result = model.move_row_action(0, LAST_ROW + 1, -10);
    assert!(result.is_err());

    // This works
    let result = model.move_row_action(0, LAST_ROW, -1);
    assert!(result.is_ok());
}

#[test]
fn test_move_row_down_absolute_refs() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    // Absolute references
    model._set("E3", "=$G$3");
    model._set("E4", "=$G$4");
    model._set("E5", "=SUM($G$3:$J$3)");
    model._set("E6", "=SUM($G$3:$G$3)");
    model._set("E7", "=SUM($G$4:$G$4)");
    model.evaluate();

    assert!(model.move_row_action(0, 3, 1).is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("E3"), "=$G$3");
    assert_eq!(model._get_formula("E4"), "=$G$4");
    assert_eq!(model._get_formula("E5"), "=SUM($G$4:$J$4)");
    assert_eq!(model._get_formula("E6"), "=SUM($G$4:$G$4)");
    assert_eq!(model._get_formula("E7"), "=SUM($G$3:$G$3)");
}

#[test]
fn test_move_column_right_absolute_refs() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    // Absolute references
    model._set("E3", "=$G$3");
    model._set("E4", "=$H$3");
    model._set("E5", "=SUM($G$3:$J$7)");
    model._set("E6", "=SUM($G$3:$G$7)");
    model._set("E7", "=SUM($H$3:$H$7)");
    model.evaluate();

    assert!(model.move_column_action(0, 7, 1).is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("E3"), "=$H$3");
    assert_eq!(model._get_formula("E4"), "=$G$3");
    assert_eq!(model._get_formula("E5"), "=SUM($H$3:$J$7)");
    assert_eq!(model._get_formula("E6"), "=SUM($H$3:$H$7)");
    assert_eq!(model._get_formula("E7"), "=SUM($G$3:$G$7)");
}

#[test]
fn test_move_row_down_mixed_refs() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    model._set("E3", "=$G3"); // absolute col, relative row
    model._set("E4", "=$G4");
    model._set("E5", "=SUM($G3:$J3)");
    model._set("E6", "=SUM($G3:$G3)");
    model._set("E7", "=SUM($G4:$G4)");
    model._set("F3", "=H$3"); // relative col, absolute row
    model._set("F4", "=G$3");
    model.evaluate();

    assert!(model.move_row_action(0, 3, 1).is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("E3"), "=$G3");
    assert_eq!(model._get_formula("E4"), "=$G4");
    assert_eq!(model._get_formula("E5"), "=SUM($G4:$J4)");
    assert_eq!(model._get_formula("E6"), "=SUM($G4:$G4)");
    assert_eq!(model._get_formula("E7"), "=SUM($G3:$G3)");
    assert_eq!(model._get_formula("F3"), "=G$4");
    assert_eq!(model._get_formula("F4"), "=H$4");
}

#[test]
fn test_move_column_right_mixed_refs() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    model._set("E3", "=$G3");
    model._set("E4", "=$H3");
    model._set("E5", "=SUM($G3:$J7)");
    model._set("E6", "=SUM($G3:$G7)");
    model._set("E7", "=SUM($H3:$H7)");
    model._set("F3", "=H$3");
    model._set("F4", "=H$3");
    model.evaluate();

    assert!(model.move_column_action(0, 7, 1).is_ok());
    model.evaluate();

    assert_eq!(model._get_formula("E3"), "=$H3");
    assert_eq!(model._get_formula("E4"), "=$G3");
    assert_eq!(model._get_formula("E5"), "=SUM($H3:$J7)");
    assert_eq!(model._get_formula("E6"), "=SUM($H3:$H7)");
    assert_eq!(model._get_formula("E7"), "=SUM($G3:$G7)");
    assert_eq!(model._get_formula("F3"), "=G$3");
    assert_eq!(model._get_formula("F4"), "=G$3");
}

#[test]
fn test_move_row_height() {
    let mut model = new_empty_model();
    let sheet = 0;
    let custom_height = DEFAULT_ROW_HEIGHT * 2.0;
    // Set a custom height for row 3
    model
        .workbook
        .worksheet_mut(sheet)
        .unwrap()
        .set_row_height(3, custom_height)
        .unwrap();

    // Record the original height of row 4 (should be the default)
    let original_row4_height = model.get_row_height(sheet, 4).unwrap();

    // Move row 3 down by one position
    assert!(model.move_row_action(sheet, 3, 1).is_ok());

    // The custom height should now be on row 4
    assert_eq!(model.get_row_height(sheet, 4), Ok(custom_height));

    // Row 3 should now have the previous height of row 4
    assert_eq!(model.get_row_height(sheet, 3), Ok(original_row4_height));
}

/// Moving a row down by two positions should shift formulas on intermediate
/// rows by only one (the row that gets skipped), not by the full delta ‒ this
/// guards against the regression fixed in the RowMove displacement logic.
#[test]
fn test_row_move_down_two_updates_intermediate_refs_by_one() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    // Set up formulas to verify intermediate rows shift by 1 (not full delta).
    model._set("E3", "=G3"); // target row
    model._set("E4", "=G4"); // intermediate row
    model._set("E5", "=SUM(G3:J3)");
    model.evaluate();

    // Move row 3 down by two positions (row 3 -> row 5)
    assert!(model.move_row_action(0, 3, 2).is_ok());
    model.evaluate();

    // Assert that references for the moved row and intermediate row are correct.
    assert_eq!(model._get_formula("E3"), "=G3");
    assert_eq!(model._get_formula("E5"), "=G5");
    assert_eq!(model._get_formula("E4"), "=SUM(G5:J5)");
}

/// Moving a column right by two positions should shift formulas on
/// intermediate columns by only one, ensuring the ColumnMove displacement
/// logic handles multi-position moves correctly.
#[test]
fn test_column_move_right_two_updates_intermediate_refs_by_one() {
    let mut model = new_empty_model();
    populate_table(&mut model);
    // Set up formulas to verify intermediate columns shift by 1 (not full delta).
    model._set("E3", "=$G3"); // target column
    model._set("E4", "=$H3"); // intermediate column
    model._set("E5", "=SUM($G3:$J7)");
    model.evaluate();

    // Move column G (7) right by two positions (G -> I)
    assert!(model.move_column_action(0, 7, 2).is_ok());
    model.evaluate();

    // Assert that references for moved and intermediate columns are correct.
    assert_eq!(model._get_formula("E3"), "=$I3");
    assert_eq!(model._get_formula("E4"), "=$G3");
    assert_eq!(model._get_formula("E5"), "=SUM($I3:$J7)");
}

// A  B  C  D  E  F  G   H  I  J   K   L   M   N   O   P   Q   R
// 1  2  3  4  5  6  7   8  9  10  11  12  13  14  15  16  17  18
