use std::error::Error;
use tuphium::error::{TuphiumError, TuphiumResult};

#[test]
fn shape_mismatch_reports_expected_and_actual_shapes() {
    let error = TuphiumError::ShapeMismatch {
        operation: "Matrix::matvec",
        expected: vec![2, 3],
        actual: vec![2, 2],
    };

    assert_eq!(
        error.to_string(),
        "Matrix::matvec: shape mismatch (expected [2, 3], got [2, 2])"
    );
}

#[test]
fn index_out_of_bounds_reports_index_and_matrix_bounds() {
    let error = TuphiumError::IndexOutOfBounds {
        operation: "Matrix::get",
        row: 3,
        col: 1,
        rows: 2,
        cols: 2,
    };

    assert_eq!(
        error.to_string(),
        "Matrix::get: index (3, 1) is outside matrix bounds (2, 2)"
    );
}

#[test]
fn row_index_out_of_bounds_reports_row_bounds() {
    let error = TuphiumError::RowIndexOutOfBounds {
        operation: "DataFrame::select_rows",
        row: 3,
        rows: 2,
    };

    assert_eq!(
        error.to_string(),
        "DataFrame::select_rows: row index 3 is outside row bounds (2)"
    );
}

#[test]
fn csv_like_errors_are_public_and_readable() {
    let extension_error = TuphiumError::InvalidFileExtension {
        operation: "read_csv",
        path: "data/input.txt".to_string(),
        expected_extension: ".csv".to_string(),
    };

    assert_eq!(
        extension_error.to_string(),
        "read_csv: invalid file extension for 'data/input.txt' (expected .csv)"
    );

    let io_error = TuphiumError::IoError {
        operation: "read_csv",
        path: Some("data/missing.csv".to_string()),
        message: "file not found".to_string(),
    };

    assert_eq!(
        io_error.to_string(),
        "read_csv: I/O error for 'data/missing.csv' (file not found)"
    );
}

#[test]
fn dataframe_like_errors_are_public_and_readable() {
    let malformed = TuphiumError::MalformedCsv {
        operation: "DataFrame::from_csv_data",
        row: 2,
        expected_fields: 4,
        actual_fields: 3,
    };

    assert_eq!(
        malformed.to_string(),
        "DataFrame::from_csv_data: malformed CSV row 2 (expected 4 fields, got 3)"
    );

    let missing_column = TuphiumError::ColumnNotFound {
        operation: "DataFrame::select",
        column: "diagnosis".to_string(),
    };

    assert_eq!(
        missing_column.to_string(),
        "DataFrame::select: column 'diagnosis' not found"
    );
}

#[test]
fn dataset_like_errors_are_public_and_readable() {
    let split_error = TuphiumError::InvalidSplitFractions {
        operation: "Dataset::split",
        train: 0.7,
        validation: 0.2,
        test: 0.2,
        sum: 1.1,
    };

    assert_eq!(
        split_error.to_string(),
        "Dataset::split: invalid split fractions (train 0.7, validation 0.2, test 0.2, sum 1.1)"
    );

    let target_error = TuphiumError::InvalidTargetShape {
        operation: "Dataset::into_numeric",
        expected_columns: 1,
        actual_columns: 2,
    };

    assert_eq!(
        target_error.to_string(),
        "Dataset::into_numeric: invalid target shape (expected 1 columns, got 2)"
    );
}

#[test]
fn backend_like_errors_are_public_and_readable() {
    let unavailable = TuphiumError::BackendUnavailable {
        operation: "Matrix::sync_to_device",
        backend: "CUDA".to_string(),
        message: "no CUDA-capable device found".to_string(),
    };

    assert_eq!(
        unavailable.to_string(),
        "Matrix::sync_to_device: backend 'CUDA' is unavailable (no CUDA-capable device found)"
    );

    let failed = TuphiumError::BackendOperationFailed {
        operation: "Matrix::matvec",
        backend: "cuBLAS".to_string(),
        message: "gemm failed".to_string(),
    };

    assert_eq!(
        failed.to_string(),
        "Matrix::matvec: backend 'cuBLAS' operation failed (gemm failed)"
    );
}

#[test]
fn result_alias_returns_a_tuphium_error() {
    fn validate_input_length(actual: usize) -> TuphiumResult<()> {
        let expected = 3;

        if actual != expected {
            return Err(TuphiumError::LengthMismatch {
                operation: "Layer::forward",
                expected,
                actual,
            });
        }

        Ok(())
    }

    assert_eq!(validate_input_length(3), Ok(()));
    assert_eq!(
        validate_input_length(2),
        Err(TuphiumError::LengthMismatch {
            operation: "Layer::forward",
            expected: 3,
            actual: 2,
        })
    );
}

#[test]
fn string_backed_errors_support_partial_eq() {
    let first = TuphiumError::ColumnTypeMismatch {
        operation: "DataFrame::to_matrix",
        column: "name".to_string(),
        expected: "numeric".to_string(),
        actual: "Text".to_string(),
    };
    let second = first.clone();

    assert_eq!(first, second);
}

#[test]
fn numeric_domain_reports_invalid_input() {
    let error = TuphiumError::NumericDomain {
        operation: "Value::log",
        input: -1.0,
        reason: "logarithm requires a positive input",
    };

    assert_eq!(
        error.to_string(),
        "Value::log: input -1 is outside the numeric domain (logarithm requires a positive input)"
    );
}

#[test]
fn length_mismatch_reports_expected_and_actual_lengths() {
    let error = TuphiumError::LengthMismatch {
        operation: "Matrix::from_data",
        expected: 4,
        actual: 2,
    };

    assert_eq!(
        error.to_string(),
        "Matrix::from_data: length mismatch (expected 4, got 2)"
    );
}

#[test]
fn invalid_dimension_reports_offending_dimension() {
    let error = TuphiumError::InvalidDimension {
        operation: "Layer::new",
        dimension: "input",
        value: 0,
        reason: "must be greater than zero",
    };

    assert_eq!(
        error.to_string(),
        "Layer::new: invalid input dimension 0 (must be greater than zero)"
    );
}

#[test]
fn dimension_overflow_reports_matrix_size() {
    let error = TuphiumError::DimensionOverflow {
        operation: "Matrix::zeros",
        rows: usize::MAX,
        cols: 2,
    };

    assert_eq!(
        error.to_string(),
        format!(
            "Matrix::zeros: matrix dimensions ({}, 2) overflow the addressable size",
            usize::MAX
        )
    );
}

#[test]
fn invalid_state_reports_what_is_wrong() {
    let error = TuphiumError::InvalidState {
        operation: "Layer::backward",
        state: "forward must be called first",
    };

    assert_eq!(
        error.to_string(),
        "Layer::backward: invalid state (forward must be called first)"
    );
}

#[test]
fn division_by_zero_reports_numerator() {
    let error = TuphiumError::DivisionByZero {
        operation: "Value::div",
        numerator: 1.0,
    };

    assert_eq!(
        error.to_string(),
        "Value::div: division by zero with numerator 1"
    );
}

#[test]
fn non_finite_value_reports_encountered_value() {
    let error = TuphiumError::NonFiniteValue {
        operation: "Layer::forward",
        value: f32::INFINITY,
    };

    assert_eq!(
        error.to_string(),
        "Layer::forward: encountered non-finite value inf"
    );
}

#[test]
fn empty_data_reports_what_is_empty() {
    let error = TuphiumError::EmptyData {
        operation: "read_csv",
        target: "CSV file".to_string(),
    };

    assert_eq!(
        error.to_string(),
        "read_csv: empty data (CSV file)"
    );
}

#[test]
fn io_error_without_path_omits_file_path() {
    let error = TuphiumError::IoError {
        operation: "write_csv",
        path: None,
        message: "permission denied".to_string(),
    };

    assert_eq!(
        error.to_string(),
        "write_csv: I/O error (permission denied)"
    );
}

#[test]
fn no_numeric_columns_reports_operation() {
    let error = TuphiumError::NoNumericColumns {
        operation: "DataFrame::to_matrix",
    };

    assert_eq!(
        error.to_string(),
        "DataFrame::to_matrix: no numeric columns available"
    );
}

#[test]
fn column_type_mismatch_reports_expected_and_actual_types() {
    let error = TuphiumError::ColumnTypeMismatch {
        operation: "DataFrame::to_matrix",
        column: "name".to_string(),
        expected: "numeric".to_string(),
        actual: "Text".to_string(),
    };

    assert_eq!(
        error.to_string(),
        "DataFrame::to_matrix: column 'name' has invalid type (expected numeric, got Text)"
    );
}

#[test]
fn numeric_errors_are_standard_rust_errors() {
    let error = TuphiumError::NumericDomain {
        operation: "Value::log",
        input: 0.0,
        reason: "logarithm requires a positive input",
    };
    let standard_error: &dyn Error = &error;

    assert_eq!(
        standard_error.to_string(),
        "Value::log: input 0 is outside the numeric domain \
         (logarithm requires a positive input)"
    );
    assert!(standard_error.source().is_none());
}
