//! SEC003: Unquoted find -exec {} Pattern
//!
//! **Rule**: Detect unquoted `{}` in `find -exec` commands
//!
//! **Why this matters**:
//! Filenames with spaces or special characters can break or execute unintended
//! commands when {} is not quoted in find -exec.
//!
//! **Auto-fix**: Safe (add quotes)
//!
//! ## Examples
//!
//! ❌ **UNSAFE**:
//! ```bash
//! find . -name "*.sh" -exec chmod +x {} \;
//! find /tmp -type f -exec rm {} \;
//! ```
//!
//! ✅ **SAFE** (auto-fixable):
//! ```bash
//! find . -name "*.sh" -exec chmod +x "{}" \;
//! find /tmp -type f -exec rm "{}" \;
//! ```

use crate::linter::{Diagnostic, Fix, LintResult, Severity, Span};

/// Check for unquoted {} in find -exec
pub fn check(source: &str) -> LintResult {
    let mut result = LintResult::new();

    for (line_num, line) in source.lines().enumerate() {
        // Look for find -exec pattern with unquoted {}
        if line.contains("find ") && line.contains("-exec") {
            // Check for {} that is NOT "{}"
            if let Some(col) = line.find(" {} ") {
                // Make sure it's not already quoted
                let before = &line[..col];
                if !before.ends_with('"') {
                    let span = Span::new(
                        line_num + 1,
                        col + 2, // Space before {}
                        line_num + 1,
                        col + 4, // {} is 2 chars
                    );

                    let diag = Diagnostic::new(
                        "SEC003",
                        Severity::Error,
                        "Unquoted {} in find -exec - filenames with spaces will break",
                        span,
                    )
                    .with_fix(Fix::new("\"{}\""));

                    result.add(diag);
                }
            }
        }
    }

    result
}

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

    #[test]
    fn test_SEC003_detects_unquoted_find_exec() {
        let script = r#"find . -name "*.sh" -exec chmod +x {} \;"#;
        let result = check(script);

        assert_eq!(result.diagnostics.len(), 1);
        let diag = &result.diagnostics[0];
        assert_eq!(diag.code, "SEC003");
        assert_eq!(diag.severity, Severity::Error);
        assert!(diag.message.contains("Unquoted"));
    }

    #[test]
    fn test_SEC003_detects_unquoted_rm() {
        let script = "find /tmp -type f -exec rm {} \\;";
        let result = check(script);

        assert_eq!(result.diagnostics.len(), 1);
    }

    #[test]
    fn test_SEC003_no_warning_with_quotes() {
        let script = r#"find . -name "*.sh" -exec chmod +x "{}" \;"#;
        let result = check(script);

        assert_eq!(result.diagnostics.len(), 0);
    }

    #[test]
    fn test_SEC003_provides_fix() {
        let script = "find . -exec cat {} \\;";
        let result = check(script);

        assert!(result.diagnostics[0].fix.is_some());
        let fix = result.diagnostics[0].fix.as_ref().unwrap();
        assert_eq!(fix.replacement, "\"{}\"");
    }

    #[test]
    fn test_SEC003_no_false_positive_no_find() {
        let script = "echo {} something";
        let result = check(script);

        assert_eq!(result.diagnostics.len(), 0);
    }

    // ===== Mutation Coverage Tests - Following SEC001 pattern (100% kill rate) =====

    #[test]
    fn test_mutation_sec003_find_exec_start_col_exact() {
        // MUTATION: Line 40:25 - replace + with * in line_num + 1
        // MUTATION: Line 41:25 - replace + with * in col + 2
        // Tests start column calculation
        let bash_code = r#"find . -name "*.sh" -exec chmod +x {} \;"#;
        let result = check(bash_code);
        assert_eq!(result.diagnostics.len(), 1);
        let span = result.diagnostics[0].span;
        // {} is at position 40-41 (after "chmod +x ")
        // col + 2 should give us the start of {}
        assert_eq!(
            span.start_col, 36,
            "Start column must use col + 2, not col * 2"
        );
    }

    #[test]
    fn test_mutation_sec003_find_exec_end_col_exact() {
        // MUTATION: Line 43:25 - replace + with * in col + 4
        // Tests end column calculation
        let bash_code = r#"find . -name "*.sh" -exec chmod +x {} \;"#;
        let result = check(bash_code);
        assert_eq!(result.diagnostics.len(), 1);
        let span = result.diagnostics[0].span;
        // {} ends at col + 4
        assert_eq!(span.end_col, 38, "End column must be col + 4, not col * 4");
    }

    #[test]
    fn test_mutation_sec003_line_num_calculation() {
        // MUTATION: Line 40:25 - replace + with * in line_num + 1
        // Tests line number calculation for multiline input
        let bash_code = "# comment\nfind . -exec rm {} \\;";
        let result = check(bash_code);
        assert_eq!(result.diagnostics.len(), 1);
        // With +1: line 2
        // With *1: line 0
        assert_eq!(
            result.diagnostics[0].span.start_line, 2,
            "Line number must use +1, not *1"
        );
    }

    #[test]
    fn test_mutation_sec003_column_with_offset() {
        // Tests column calculations with leading whitespace
        let bash_code = "    find . -exec cat {} \\;"; // {} at different position due to indent
        let result = check(bash_code);
        assert_eq!(result.diagnostics.len(), 1);
        let span = result.diagnostics[0].span;
        // Verify arithmetic is correct with offset
        assert!(
            span.start_col > 20,
            "Must account for command and whitespace"
        );
        assert_eq!(
            span.end_col - span.start_col,
            2,
            "Span should cover {{}} (2 chars)"
        );
    }
}
