//! Tests for invalid schema transformations and error cases
//!
//! These tests validate that the type checker correctly identifies and reports
//! errors in queries that reference non-existent fields, use incorrect types,
//! or perform invalid operations.

use crate::err::TranslationErrors;
use crate::tree::ast::pipeline::Pipeline;
use crate::tree::ast::{IntoTyped, ParseWithErrors, TypeCheckExecutor};
use crate::tree::tests::shared::catalog_with_tables;
use crate::types::{Type, BOOLEAN, INT, STRING};
use rstest::rstest;

// ======================== Error Tests with LET ========================

#[rstest]
// SELECT errors - non-existent fields
#[case::select_nonexistent_field(
    vec![("name", STRING), ("age", INT)],
    "SELECT email",
    1
)]
#[case::select_multiple_nonexistent_fields(
    vec![("name", STRING)],
    "SELECT age, email, phone",
    1
)]
#[case::select_mix_valid_invalid(
    vec![("name", STRING), ("age", INT)],
    "SELECT name, email",
    1
)]
// WHERE errors - non-existent fields
#[case::where_nonexistent_field(
    vec![("name", STRING), ("age", INT)],
    "WHERE email == 'test@example.com'",
    1
)]
#[case::where_nonexistent_in_and(
    vec![("name", STRING)],
    "WHERE name == 'Alice' AND age > 18",
    1
)]
#[case::where_both_sides_nonexistent(
    vec![("name", STRING)],
    "WHERE email == phone",
    1
)]
// LET errors - non-existent fields
#[case::let_nonexistent_source(
    vec![("name", STRING)],
    "LET full_name = email",
    1
)]
#[case::let_nonexistent_in_expression(
    vec![("age", INT)],
    "LET result = age + missing_field",
    1
)]
#[case::let_multiple_missing(
    vec![("x", INT)],
    "LET result = x + y * z",
    1
)]
// AGG errors - non-existent fields
#[case::agg_nonexistent_field(
    vec![("amount", INT)],
    "AGG total = sum(missing_field)",
    1
)]
#[case::agg_nonexistent_group_by(
    vec![("amount", INT)],
    "AGG total = sum(amount) BY region",
    1
)]
#[case::agg_both_missing(
    vec![("price", INT)],
    "AGG total = sum(amount) BY region",
    1
)]
// SORT errors - non-existent fields
#[case::sort_nonexistent_field(
    vec![("name", STRING)],
    "SORT age DESC",
    1
)]
#[case::sort_multiple_nonexistent(
    vec![("name", STRING)],
    "SORT age DESC, email ASC",
    1
)]
#[case::sort_mix_valid_invalid(
    vec![("name", STRING), ("age", INT)],
    "SORT name ASC, email DESC",
    1
)]
// DROP errors - non-existent fields
#[case::drop_nonexistent_field(
    vec![("name", STRING), ("age", INT)],
    "DROP email",
    1
)]
#[case::drop_multiple_nonexistent(
    vec![("name", STRING)],
    "DROP age, email",
    1
)]
#[case::drop_mix_valid_invalid(
    vec![("name", STRING), ("age", INT)],
    "DROP name, email",
    1
)]
// Function call errors
#[case::undefined_function(
    vec![("name", STRING)],
    "SELECT result = undefined_function(name)",
    1
)]
#[case::function_with_nonexistent_field(
    vec![("name", STRING)],
    "SELECT result = len(email)",
    1
)]
// Field access after DROP
#[case::dropped_field_in_select(
    vec![("name", STRING), ("age", INT), ("email", STRING)],
    "DROP email | SELECT name, email",
    1
)]
#[case::dropped_field_in_where(
    vec![("name", STRING), ("age", INT), ("active", BOOLEAN)],
    "DROP active | WHERE active == true",
    1
)]
// AGG schema transformation errors
#[case::non_grouped_field_after_agg(
    vec![("region", STRING), ("category", STRING), ("amount", INT)],
    "AGG total = sum(amount) BY region | SELECT region, category",
    1
)]
#[case::original_field_after_agg(
    vec![("region", STRING), ("amount", INT)],
    "AGG total = sum(amount) BY region | SELECT amount",
    1
)]
fn test_error_cases_with_let(
    #[case] input_fields: Vec<(&str, Type)>,
    #[case] command: &str,
    #[case] min_errors: usize,
) -> Result<(), TranslationErrors> {
    // Build a LET command that establishes the input schema
    let let_clause = input_fields
        .iter()
        .map(|(name, typ)| {
            let value = match typ {
                t if t == &STRING => "'dummy'",
                t if t == &INT => "0",
                t if t == &BOOLEAN => "true",
                _ => "'unknown'",
            };
            format!("{} = {}", name, value)
        })
        .collect::<Vec<_>>()
        .join(", ");

    let query = format!("LET {} | {}", let_clause, command);
    let pipeline = Pipeline::parse_result(&query)?;

    let (_typed, errors) = pipeline.typed_with_errors();
    assert!(errors.len() >= min_errors);
    Ok(())
}

// ======================== Error Tests with FROM ========================

#[rstest]
// FROM errors - non-existent tables
#[case::from_nonexistent_table(
    vec![],
    "FROM missing_table",
    1
)]
#[case::from_empty_catalog(
    vec![],
    "FROM users",
    1
)]
// Pipeline error propagation
#[case::error_in_first_command(
    vec![("users", vec![("name", STRING), ("age", INT)])],
    "FROM users | SELECT email",
    1
)]
#[case::error_in_middle_command(
    vec![("users", vec![("name", STRING), ("age", INT)])],
    "FROM users | WHERE email == 'test' | SELECT name",
    1
)]
#[case::error_in_last_command(
    vec![("users", vec![("name", STRING), ("age", INT)])],
    "FROM users | WHERE age > 18 | SELECT name, email",
    1
)]
#[case::multiple_errors_in_pipeline(
    vec![("users", vec![("name", STRING)])],
    "FROM users | WHERE age > 18 | SELECT name, email",
    2
)]
fn test_error_cases_with_from(
    #[case] tables: Vec<(&str, Vec<(&str, Type)>)>,
    #[case] query: &str,
    #[case] min_errors: usize,
) -> Result<(), TranslationErrors> {
    let pipeline = Pipeline::parse_result(query)?;

    let catalog = catalog_with_tables(tables);
    let (_typed, errors) = pipeline
        .typed_with()
        .with_provider(catalog)
        .typed_with_errors();

    assert!(errors.len() >= min_errors);
    Ok(())
}

// ======================== Type Error Resilience Tests ========================

use crate::tree::ast::expression::Expression;

#[rstest]
// Invalid binary operations
#[case::string_add_boolean("'hello' + true", "could not find a matching function")]
#[case::string_multiply_int("'hello' * 5", "could not find a matching function")]
#[case::boolean_add_int("true + 5", "could not find a matching function")]
#[case::string_subtract("'hello' - 'world'", "could not find a matching function")]
// Invalid function arguments
#[case::len_int("len(42)", "matching function definition")]
#[case::len_boolean("len(true)", "matching function definition")]
#[case::upper_int("upper(42)", "matching function definition")]
#[case::abs_string("abs('hello')", "matching function definition")]
#[case::round_string("round('3.14')", "matching function definition")]
// Unknown functions
#[case::unknown_function("unknown_func(42)", "no function by this name")]
#[case::undefined_function("not_a_function('test')", "no function by this name")]
// Mixed-type array errors
#[case::mixed_array_int_string("[1, 'two', 3]", "could not determine array element type")]
#[case::mixed_array_bool_int("[true, 1, false]", "could not determine array element type")]
fn test_type_errors(#[case] input: &str, #[case] expected_error: &str) {
    let result = Expression::parse_result(input).unwrap().typed_result();
    assert!(
        result.is_err(),
        "Expected error but got success for: {}",
        input
    );

    let err = result.unwrap_err();
    let err_string = err.to_string();

    // Print the error for debugging (shows ariadne formatted output)
    eprintln!("Error for '{}': {}", input, err_string);

    assert!(
        err_string.contains(expected_error),
        "Error '{}' does not contain expected fragment '{}'",
        err_string,
        expected_error
    );
}

// ======================== Duplicate Field Error Tests ========================

#[rstest]
// SELECT duplicate fields
#[case::select_duplicate_same_type(
    vec![("x", INT), ("y", INT)],
    "SELECT a = x, a = y",
    1
)]
#[case::select_duplicate_diff_type(
    vec![("x", INT), ("y", STRING)],
    "SELECT a = x, a = y",
    1
)]
#[case::select_nested_struct_conflict(
    vec![("x", INT), ("y", STRING)],
    "LET result = {x: x, y: y} | SELECT result, result.x = 'conflict'",
    1
)]
// LET duplicate fields
#[case::let_duplicate_same_type(
    vec![("x", INT), ("y", INT)],
    "LET a = x, a = y",
    1
)]
#[case::let_duplicate_diff_type(
    vec![("x", INT), ("y", STRING)],
    "LET a = x, a = y",
    1
)]
// WINDOW duplicate fields
#[case::window_duplicate_same_type(
    vec![("x", INT)],
    "WINDOW a = count(), a = sum(x)",
    1
)]
// AGG duplicate fields
#[case::agg_duplicate_same_type(
    vec![("x", INT), ("y", STRING)],
    "AGG a = count(), a = sum(x) BY y",
    1
)]
#[case::agg_duplicate_groupby_conflict(
    vec![("x", INT), ("y", STRING)],
    "AGG x = count() BY x",
    1
)]
fn test_duplicate_field_errors(
    #[case] input_fields: Vec<(&str, Type)>,
    #[case] pipeline: &str,
    #[case] min_errors: usize,
) -> Result<(), TranslationErrors> {
    // Build a LET command that establishes the input schema
    let let_clause = input_fields
        .iter()
        .map(|(name, typ)| {
            let value = match typ {
                t if t == &STRING => "'dummy'",
                t if t == &INT => "0",
                t if t == &BOOLEAN => "true",
                _ => "'unknown'",
            };
            format!("{} = {}", name, value)
        })
        .collect::<Vec<_>>()
        .join(", ");

    let query = format!("LET {} | {}", let_clause, pipeline);
    let parsed = Pipeline::parse_result(&query)?;

    let (_typed, errors) = parsed.typed_with_errors();
    assert!(
        errors.len() >= min_errors,
        "Expected at least {} errors, got {}",
        min_errors,
        errors.len()
    );
    Ok(())
}

// ======================== Misplaced Side-Effect Command Tests ========================

#[rstest]
// APPEND in non-final position
#[case::append_then_select(
    vec![("events", vec![("id", INT), ("name", STRING)])],
    "FROM events | APPEND output_table | SELECT id",
    "APPEND command must be the final command"
)]
#[case::append_then_where(
    vec![("events", vec![("id", INT), ("name", STRING)])],
    "FROM events | APPEND output_table | WHERE id > 0",
    "APPEND command must be the final command"
)]
#[case::append_in_middle(
    vec![("events", vec![("id", INT), ("name", STRING)])],
    "FROM events | APPEND output_table | LET x = 1 | SELECT id",
    "APPEND command must be the final command"
)]
fn test_misplaced_side_effect_commands(
    #[case] tables: Vec<(&str, Vec<(&str, Type)>)>,
    #[case] query: &str,
    #[case] expected_error: &str,
) -> Result<(), TranslationErrors> {
    let pipeline = Pipeline::parse_result(query)?;

    let catalog = catalog_with_tables(tables);
    let (_typed, errors) = pipeline
        .typed_with()
        .with_provider(catalog)
        .typed_with_errors();

    // Print errors for debugging
    eprintln!("Errors: {}", errors);

    assert!(
        !errors.is_empty(),
        "Expected at least one error for misplaced side-effect command"
    );

    let error_string = errors.to_string();
    assert!(
        error_string.contains(expected_error),
        "Error '{}' does not contain expected fragment '{}'",
        error_string,
        expected_error
    );

    Ok(())
}

// ======================== Parse Error Pipeline Tests ========================
//
// When a query has parse errors (QueryKind::Error), the resulting TypedStatement
// should have a pipeline with kind: Err, not kind: Ok(ValidPipeline { commands: vec![] }).
// This maintains the invariant that Ok(ValidPipeline) always has at least one command.

use crate::tree::ast::query::Query;

#[rstest]
#[case::garbage_input("!@#$%")]
#[case::empty_input("")]
fn test_parse_error_query_produces_error_pipeline(#[case] input: &str) {
    let (query, _parse_errors) = Query::parse_with_errors(input);
    let typed = query.typed();

    assert!(
        typed.pipeline.kind.is_err(),
        "Expected pipeline.kind to be Err for parse-error query '{}', but got Ok",
        input
    );
}
