//! The iterative fix engine.
//!
//! Validates SQL, identifies fixable errors, applies fixes, and re-validates
//! in a loop until the query is valid or no more fixes can be applied.

use std::time::Instant;

use crate::schema::types::SchemaDefinition;
use crate::validator::engine::validate;

use super::rewriter::SqlRewriter;
use super::strategies::find_fix;
use super::types::{FixConfig, FixResult, UnfixableError};

/// Fix a SQL query by iteratively validating and applying fixes.
///
/// The engine loops up to `config.max_iterations` times:
/// 1. Validate the current SQL
/// 2. If valid, stop (success)
/// 3. For each error, attempt to find a fix
/// 4. If no fixes found, stop (nothing more we can do)
/// 5. Apply all fixes via the rewriter
/// 6. Continue with the fixed SQL
///
/// After the loop, optionally re-validates the final SQL to confirm validity.
///
/// # Example
///
/// ```rust,no_run
/// use altimate_core::schema::types::SchemaDefinition;
/// use altimate_core::fixer::types::FixConfig;
/// use altimate_core::fixer::engine::fix;
///
/// # async fn example() {
/// let schema = SchemaDefinition::from_yaml_file("schema.yaml").unwrap();
/// let result = fix("SELECT * FROM usrs", &schema, &FixConfig::default()).await;
/// if result.fixed {
///     println!("Fixed SQL: {}", result.fixed_sql);
/// }
/// # }
/// ```
pub async fn fix(sql: &str, schema: &SchemaDefinition, config: &FixConfig) -> FixResult {
    let start = Instant::now();
    let mut current_sql = sql.to_string();
    let mut all_fixes = Vec::new();
    let mut iterations = 0;

    for _ in 0..config.max_iterations {
        iterations += 1;

        // Validate current SQL
        let result = validate(&current_sql, schema).await;

        if result.valid {
            // Query is already valid — no fixes needed (or all fixes worked)
            let elapsed = start.elapsed().as_millis() as u64;
            return FixResult {
                original_sql: sql.to_string(),
                fixed: !all_fixes.is_empty(),
                fixed_sql: current_sql,
                fixes_applied: all_fixes,
                unfixable_errors: vec![],
                post_fix_valid: true,
                iterations,
                fix_time_ms: elapsed,
            };
        }

        // Try to find fixes for each error
        let mut round_fixes = Vec::new();
        let mut unfixable = Vec::new();

        for error in &result.errors {
            match find_fix(error, schema, config) {
                Some(fix_action) => {
                    round_fixes.push(fix_action);
                }
                None => {
                    unfixable.push(UnfixableError {
                        error: error.clone(),
                        reason: format!("No automatic fix available for {}", error.code),
                    });
                }
            }
        }

        if round_fixes.is_empty() {
            // No fixes found — return what we have
            let elapsed = start.elapsed().as_millis() as u64;
            return FixResult {
                original_sql: sql.to_string(),
                fixed: !all_fixes.is_empty(),
                fixed_sql: current_sql,
                fixes_applied: all_fixes,
                unfixable_errors: unfixable,
                post_fix_valid: false,
                iterations,
                fix_time_ms: elapsed,
            };
        }

        // Apply fixes via rewriter
        let mut rewriter = SqlRewriter::new(&current_sql);
        for fix_action in &round_fixes {
            rewriter.apply(fix_action);
        }
        current_sql = rewriter.finish();

        all_fixes.extend(round_fixes);
    }

    // Post-loop: optionally re-validate
    let post_fix_valid = if config.revalidate {
        let final_result = validate(&current_sql, schema).await;
        final_result.valid
    } else {
        false
    };

    // Collect any remaining unfixable errors from a final validation
    let unfixable_errors = if config.revalidate {
        let final_result = validate(&current_sql, schema).await;
        final_result
            .errors
            .into_iter()
            .map(|e| {
                let reason = format!("Still present after {iterations} fix iterations");
                UnfixableError { error: e, reason }
            })
            .collect()
    } else {
        vec![]
    };

    let elapsed = start.elapsed().as_millis() as u64;
    FixResult {
        original_sql: sql.to_string(),
        fixed: !all_fixes.is_empty(),
        fixed_sql: current_sql,
        fixes_applied: all_fixes,
        unfixable_errors,
        post_fix_valid,
        iterations,
        fix_time_ms: elapsed,
    }
}

/// Fix a SQL query with default configuration.
///
/// Convenience wrapper around [`fix`] with [`FixConfig::default()`].
pub async fn fix_default(sql: &str, schema: &SchemaDefinition) -> FixResult {
    fix(sql, schema, &FixConfig::default()).await
}

#[cfg(test)]
mod tests {
    use super::*;

    fn ecommerce_schema() -> SchemaDefinition {
        SchemaDefinition::from_yaml(include_str!(
            "../../../../tests/fixtures/schemas/ecommerce.yaml"
        ))
        .expect("Failed to load ecommerce schema fixture")
    }

    #[tokio::test]
    async fn test_fix_valid_sql_returns_unchanged() {
        let schema = ecommerce_schema();
        let result = fix_default("SELECT id, email FROM users", &schema).await;
        assert!(!result.fixed);
        assert_eq!(result.fixed_sql, "SELECT id, email FROM users");
        assert!(result.post_fix_valid);
        assert!(result.fixes_applied.is_empty());
    }

    #[tokio::test]
    async fn test_fix_misspelled_table() {
        let schema = ecommerce_schema();
        let result = fix_default("SELECT * FROM usrs", &schema).await;
        assert!(result.fixed);
        assert_eq!(result.fixed_sql, "SELECT * FROM users");
        assert!(result.post_fix_valid);
        assert_eq!(result.fixes_applied.len(), 1);
        assert_eq!(result.fixes_applied[0].original, "usrs");
        assert_eq!(result.fixes_applied[0].replacement, "users");
    }

    #[tokio::test]
    async fn test_fix_unfixable_syntax_error() {
        let schema = ecommerce_schema();
        let result = fix_default("SELECTZ * FROM users", &schema).await;
        assert!(!result.fixed);
        assert!(!result.post_fix_valid);
        assert!(!result.unfixable_errors.is_empty());
    }

    #[tokio::test]
    async fn test_fix_respects_max_iterations() {
        let schema = ecommerce_schema();
        let config = FixConfig {
            max_iterations: 1,
            ..Default::default()
        };
        let result = fix("SELECT * FROM usrs", &schema, &config).await;
        assert!(result.iterations <= 2); // 1 iteration + possible revalidation step
    }

    #[tokio::test]
    async fn test_fix_respects_confidence_threshold() {
        let schema = ecommerce_schema();
        // Very high threshold — unlikely to meet
        let config = FixConfig {
            min_confidence: 0.99,
            ..Default::default()
        };
        let result = fix("SELECT * FROM usrs", &schema, &config).await;
        // With 0.99 threshold, "usrs" -> "users" might not pass
        // The result depends on the actual Jaro-Winkler score
        assert!(result.iterations >= 1);
    }
}
