//! Fix strategies for different validation error kinds.
//!
//! Each strategy examines a [`ValidationError`] and its suggestions to
//! produce a [`FixAction`] when possible. Strategies dispatch based on
//! [`ErrorKind`] and require a minimum confidence threshold.

use crate::errors::types::{ErrorKind, SuggestionKind, ValidationError};
use crate::schema::types::SchemaDefinition;

use super::types::{FixAction, FixActionType, FixConfig};

/// Try to find a fix for a validation error.
///
/// Dispatches to the appropriate strategy based on the error kind.
/// Returns `None` if the error is unfixable or no suggestion meets
/// the confidence threshold.
pub fn find_fix(
    error: &ValidationError,
    _schema: &SchemaDefinition,
    config: &FixConfig,
) -> Option<FixAction> {
    match &error.kind {
        ErrorKind::TableNotFound { table } => fix_table_not_found(table, error, config),
        ErrorKind::ColumnNotFound { column, .. } => fix_column_not_found(column, error, config),
        ErrorKind::AmbiguousColumn { column, .. } => fix_ambiguous_column(column, error, config),
        ErrorKind::TypeMismatch { .. }
        | ErrorKind::SyntaxError
        | ErrorKind::InvalidJoin { .. }
        | ErrorKind::UnsupportedDialectFeature { .. }
        | ErrorKind::Other { .. } => None,
    }
}

/// Fix a table-not-found error by replacing with the best fuzzy match.
fn fix_table_not_found(
    table: &str,
    error: &ValidationError,
    config: &FixConfig,
) -> Option<FixAction> {
    best_did_you_mean(error, config.min_confidence).map(|(name, confidence)| FixAction {
        action: FixActionType::ReplaceTable,
        original: table.to_string(),
        replacement: name.clone(),
        confidence,
        explanation: format!("Replace table '{table}' with '{name}'"),
        location: error.location.clone(),
    })
}

/// Fix a column-not-found error by replacing with the best fuzzy match.
fn fix_column_not_found(
    column: &str,
    error: &ValidationError,
    config: &FixConfig,
) -> Option<FixAction> {
    best_did_you_mean(error, config.min_confidence).map(|(name, confidence)| {
        // The suggestion name may be "table.column" — extract just the column part
        // for the replacement since the original SQL likely has just the column name.
        let replacement = name.rsplit('.').next().unwrap_or(&name).to_string();
        FixAction {
            action: FixActionType::ReplaceColumn,
            original: column.to_string(),
            replacement,
            confidence,
            explanation: format!("Replace column '{column}' with '{name}'"),
            location: error.location.clone(),
        }
    })
}

/// Fix an ambiguous column by qualifying it with a table name.
///
/// Uses the first `AddQualification` suggestion if available,
/// otherwise falls back to `DidYouMean` suggestions.
fn fix_ambiguous_column(
    column: &str,
    error: &ValidationError,
    config: &FixConfig,
) -> Option<FixAction> {
    // First try AddQualification suggestions
    for suggestion in &error.suggestions {
        if let SuggestionKind::AddQualification { qualified } = &suggestion.kind
            && suggestion.confidence >= config.min_confidence
        {
            return Some(FixAction {
                action: FixActionType::QualifyColumn,
                original: column.to_string(),
                replacement: qualified.clone(),
                confidence: suggestion.confidence,
                explanation: format!("Qualify ambiguous column '{column}' as '{qualified}'"),
                location: error.location.clone(),
            });
        }
    }

    // Fallback: use DidYouMean if it contains a qualified name (table.column)
    for suggestion in &error.suggestions {
        if let SuggestionKind::DidYouMean { name } = &suggestion.kind
            && suggestion.confidence >= config.min_confidence
            && name.contains('.')
        {
            return Some(FixAction {
                action: FixActionType::QualifyColumn,
                original: column.to_string(),
                replacement: name.clone(),
                confidence: suggestion.confidence,
                explanation: format!("Qualify ambiguous column '{column}' as '{name}'"),
                location: error.location.clone(),
            });
        }
    }

    None
}

/// Find the best `DidYouMean` suggestion that meets the confidence threshold.
///
/// Returns `(suggested_name, confidence)` for the highest-confidence match.
fn best_did_you_mean(error: &ValidationError, min_confidence: f64) -> Option<(String, f64)> {
    error
        .suggestions
        .iter()
        .filter_map(|s| {
            if let SuggestionKind::DidYouMean { name } = &s.kind
                && s.confidence >= min_confidence
            {
                return Some((name.clone(), s.confidence));
            }
            None
        })
        .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::errors::types::{ErrorCode, Suggestion};

    fn make_config() -> FixConfig {
        FixConfig::default()
    }

    fn make_schema() -> SchemaDefinition {
        SchemaDefinition::from_yaml(
            r#"
version: "1"
tables:
  users:
    columns:
      - name: id
        type: INT
      - name: email
        type: VARCHAR
"#,
        )
        .expect("test schema")
    }

    #[test]
    fn test_fix_table_not_found_with_suggestion() {
        let error = ValidationError {
            code: ErrorCode::E001,
            kind: ErrorKind::TableNotFound {
                table: "usrs".to_string(),
            },
            message: "Table 'usrs' not found".to_string(),
            location: None,
            suggestions: vec![Suggestion {
                kind: SuggestionKind::DidYouMean {
                    name: "users".to_string(),
                },
                message: "Did you mean \"users\"?".to_string(),
                confidence: 0.93,
            }],
        };

        let fix = find_fix(&error, &make_schema(), &make_config());
        assert!(fix.is_some());
        let fix = fix.unwrap();
        assert_eq!(fix.original, "usrs");
        assert_eq!(fix.replacement, "users");
        assert!(matches!(fix.action, FixActionType::ReplaceTable));
    }

    #[test]
    fn test_fix_table_not_found_low_confidence() {
        let error = ValidationError {
            code: ErrorCode::E001,
            kind: ErrorKind::TableNotFound {
                table: "zzz".to_string(),
            },
            message: "Table 'zzz' not found".to_string(),
            location: None,
            suggestions: vec![Suggestion {
                kind: SuggestionKind::DidYouMean {
                    name: "users".to_string(),
                },
                message: "Did you mean \"users\"?".to_string(),
                confidence: 0.5,
            }],
        };

        let fix = find_fix(&error, &make_schema(), &make_config());
        assert!(fix.is_none());
    }

    #[test]
    fn test_fix_column_not_found_with_suggestion() {
        let error = ValidationError {
            code: ErrorCode::E002,
            kind: ErrorKind::ColumnNotFound {
                table: None,
                column: "emal".to_string(),
            },
            message: "Column 'emal' not found".to_string(),
            location: None,
            suggestions: vec![Suggestion {
                kind: SuggestionKind::DidYouMean {
                    name: "users.email".to_string(),
                },
                message: "Did you mean \"users.email\"?".to_string(),
                confidence: 0.91,
            }],
        };

        let fix = find_fix(&error, &make_schema(), &make_config());
        assert!(fix.is_some());
        let fix = fix.unwrap();
        assert_eq!(fix.original, "emal");
        assert_eq!(fix.replacement, "email");
        assert!(matches!(fix.action, FixActionType::ReplaceColumn));
    }

    #[test]
    fn test_syntax_error_is_unfixable() {
        let error = ValidationError {
            code: ErrorCode::E000,
            kind: ErrorKind::SyntaxError,
            message: "Syntax error near 'SELECTZ'".to_string(),
            location: None,
            suggestions: vec![],
        };

        let fix = find_fix(&error, &make_schema(), &make_config());
        assert!(fix.is_none());
    }

    #[test]
    fn test_no_suggestions_returns_none() {
        let error = ValidationError {
            code: ErrorCode::E001,
            kind: ErrorKind::TableNotFound {
                table: "xyz".to_string(),
            },
            message: "Table 'xyz' not found".to_string(),
            location: None,
            suggestions: vec![],
        };

        let fix = find_fix(&error, &make_schema(), &make_config());
        assert!(fix.is_none());
    }
}
