//! Integration tests for SARIF 2.1.0 output across all validation types.
//!
//! Tests cover validation errors, lint findings, safety threats,
//! migration risks, valid/clean inputs, and JSON structure correctness.

use altimate_core::errors::types::{
    ErrorCode, ErrorKind, SourceLocation, ValidationError, ValidationResult,
};
use altimate_core::linter::types::{LintFinding, LintResult, LintSeverity};
use altimate_core::migration::types::{MigrationFinding, MigrationResult, MigrationRisk};
use altimate_core::output::sarif::{
    lint_to_sarif, migration_to_sarif, safety_to_sarif, validation_to_sarif,
};
use altimate_core::safety::types::{SafetyRule, SafetyScanResult, SafetySeverity, ThreatFinding};

const SARIF_SCHEMA_URL: &str = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json";

// ═══════════════════════════════════════════════════════════════════════
// Validation SARIF
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn sarif_validation_valid_sql_zero_results() {
    let result = ValidationResult::ok(Default::default());
    let sarif = validation_to_sarif(&result, "test.sql");
    assert_eq!(sarif.version, "2.1.0");
    assert_eq!(sarif.schema, SARIF_SCHEMA_URL);
    assert_eq!(sarif.runs.len(), 1);
    assert!(sarif.runs[0].results.is_empty());
    assert!(sarif.runs[0].tool.driver.rules.is_empty());
}

#[test]
fn sarif_validation_table_not_found() {
    let result = ValidationResult::with_errors(vec![ValidationError {
        code: ErrorCode::E001,
        kind: ErrorKind::TableNotFound {
            table: "nonexistent".to_string(),
        },
        message: "Table 'nonexistent' not found in schema".to_string(),
        location: Some(SourceLocation {
            line: 1,
            column: 15,
        }),
        suggestions: vec![],
    }]);
    let sarif = validation_to_sarif(&result, "query.sql");
    assert_eq!(sarif.runs[0].results.len(), 1);
    assert_eq!(sarif.runs[0].results[0].rule_id, "E001");
    assert_eq!(sarif.runs[0].results[0].level, "error");
    assert!(
        sarif.runs[0].results[0]
            .message
            .text
            .contains("nonexistent")
    );

    let loc = &sarif.runs[0].results[0].locations[0].physical_location;
    assert_eq!(loc.artifact_location.uri, "query.sql");
    assert_eq!(loc.region.start_line, 1);
    assert_eq!(loc.region.start_column, Some(15));
}

#[test]
fn sarif_validation_column_not_found() {
    let result = ValidationResult::with_errors(vec![ValidationError {
        code: ErrorCode::E002,
        kind: ErrorKind::ColumnNotFound {
            table: Some("users".to_string()),
            column: "namee".to_string(),
        },
        message: "Column 'namee' not found in table 'users'".to_string(),
        location: Some(SourceLocation { line: 1, column: 8 }),
        suggestions: vec![],
    }]);
    let sarif = validation_to_sarif(&result, "query.sql");
    assert_eq!(sarif.runs[0].results.len(), 1);
    assert_eq!(sarif.runs[0].results[0].rule_id, "E002");
    assert_eq!(sarif.runs[0].tool.driver.rules[0].id, "E002");
}

#[test]
fn sarif_validation_multiple_errors_same_code_deduplicates_rules() {
    let result = ValidationResult::with_errors(vec![
        ValidationError {
            code: ErrorCode::E001,
            kind: ErrorKind::TableNotFound {
                table: "a".to_string(),
            },
            message: "Table 'a' not found".to_string(),
            location: None,
            suggestions: vec![],
        },
        ValidationError {
            code: ErrorCode::E001,
            kind: ErrorKind::TableNotFound {
                table: "b".to_string(),
            },
            message: "Table 'b' not found".to_string(),
            location: None,
            suggestions: vec![],
        },
    ]);
    let sarif = validation_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results.len(), 2);
    assert_eq!(sarif.runs[0].tool.driver.rules.len(), 1);
}

#[test]
fn sarif_validation_multiple_different_error_codes() {
    let result = ValidationResult::with_errors(vec![
        ValidationError {
            code: ErrorCode::E000,
            kind: ErrorKind::SyntaxError,
            message: "Syntax error".to_string(),
            location: None,
            suggestions: vec![],
        },
        ValidationError {
            code: ErrorCode::E001,
            kind: ErrorKind::TableNotFound {
                table: "x".to_string(),
            },
            message: "Table not found".to_string(),
            location: None,
            suggestions: vec![],
        },
        ValidationError {
            code: ErrorCode::E002,
            kind: ErrorKind::ColumnNotFound {
                table: None,
                column: "y".to_string(),
            },
            message: "Column not found".to_string(),
            location: None,
            suggestions: vec![],
        },
    ]);
    let sarif = validation_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results.len(), 3);
    assert_eq!(sarif.runs[0].tool.driver.rules.len(), 3);
}

#[test]
fn sarif_validation_no_location_defaults_to_line_1() {
    let result = ValidationResult::with_errors(vec![ValidationError {
        code: ErrorCode::E099,
        kind: ErrorKind::Other {
            detail: "something".to_string(),
        },
        message: "Some error".to_string(),
        location: None,
        suggestions: vec![],
    }]);
    let sarif = validation_to_sarif(&result, "test.sql");
    let loc = &sarif.runs[0].results[0].locations[0].physical_location;
    assert_eq!(loc.region.start_line, 1);
    assert!(loc.region.start_column.is_none());
}

// ═══════════════════════════════════════════════════════════════════════
// Lint SARIF
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn sarif_lint_clean_sql_zero_results() {
    let result = LintResult {
        sql: "SELECT id FROM users".to_string(),
        findings: vec![],
        error_count: 0,
        warning_count: 0,
        clean: true,
    };
    let sarif = lint_to_sarif(&result, "test.sql");
    assert!(sarif.runs[0].results.is_empty());
}

#[test]
fn sarif_lint_select_star() {
    let result = LintResult {
        sql: "SELECT * FROM users".to_string(),
        findings: vec![LintFinding {
            code: "L001".to_string(),
            rule: "select_star".to_string(),
            severity: LintSeverity::Warning,
            message: "Avoid SELECT *; list columns explicitly".to_string(),
            suggestion: Some("Replace SELECT * with explicit column list".to_string()),
            line: Some(1),
            column: Some(8),
        }],
        error_count: 0,
        warning_count: 1,
        clean: false,
    };
    let sarif = lint_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results.len(), 1);
    assert_eq!(sarif.runs[0].results[0].rule_id, "L001");
    assert_eq!(sarif.runs[0].results[0].level, "warning");
}

#[test]
fn sarif_lint_error_severity() {
    let result = LintResult {
        sql: "DELETE FROM users".to_string(),
        findings: vec![LintFinding {
            code: "L002".to_string(),
            rule: "missing_where".to_string(),
            severity: LintSeverity::Error,
            message: "DELETE without WHERE".to_string(),
            suggestion: None,
            line: Some(1),
            column: None,
        }],
        error_count: 1,
        warning_count: 0,
        clean: false,
    };
    let sarif = lint_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results[0].level, "error");
}

#[test]
fn sarif_lint_info_severity() {
    let result = LintResult {
        sql: "SELECT 1".to_string(),
        findings: vec![LintFinding {
            code: "L099".to_string(),
            rule: "info_rule".to_string(),
            severity: LintSeverity::Info,
            message: "Informational notice".to_string(),
            suggestion: None,
            line: None,
            column: None,
        }],
        error_count: 0,
        warning_count: 0,
        clean: false,
    };
    let sarif = lint_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results[0].level, "note");
}

#[test]
fn sarif_lint_deduplicates_rules() {
    let result = LintResult {
        sql: "SELECT * FROM a, b".to_string(),
        findings: vec![
            LintFinding {
                code: "L001".to_string(),
                rule: "select_star".to_string(),
                severity: LintSeverity::Warning,
                message: "Avoid SELECT *".to_string(),
                suggestion: None,
                line: Some(1),
                column: None,
            },
            LintFinding {
                code: "L001".to_string(),
                rule: "select_star".to_string(),
                severity: LintSeverity::Warning,
                message: "Avoid SELECT * (second occurrence)".to_string(),
                suggestion: None,
                line: Some(2),
                column: None,
            },
        ],
        error_count: 0,
        warning_count: 2,
        clean: false,
    };
    let sarif = lint_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results.len(), 2);
    assert_eq!(sarif.runs[0].tool.driver.rules.len(), 1);
}

// ═══════════════════════════════════════════════════════════════════════
// Safety SARIF
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn sarif_safety_safe_sql_zero_results() {
    let result = SafetyScanResult {
        safe: true,
        threats: vec![],
        risk_score: 0.0,
        statement_count: 1,
        statement_types: vec!["SELECT".to_string()],
    };
    let sarif = safety_to_sarif(&result, "test.sql");
    assert!(sarif.runs[0].results.is_empty());
}

#[test]
fn sarif_safety_tautology_attack() {
    let result = SafetyScanResult {
        safe: false,
        threats: vec![ThreatFinding {
            rule: SafetyRule::TautologyAttack,
            severity: SafetySeverity::High,
            message: "Tautology detected: OR 1=1".to_string(),
            detail: "Always-true condition bypasses WHERE clause".to_string(),
            location: Some((30, 6)),
            matched_pattern: "1=1".to_string(),
        }],
        risk_score: 0.8,
        statement_count: 1,
        statement_types: vec!["SELECT".to_string()],
    };
    let sarif = safety_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results.len(), 1);
    assert_eq!(sarif.runs[0].results[0].rule_id, "safety/tautology_attack");
    assert_eq!(sarif.runs[0].results[0].level, "error");
}

#[test]
fn sarif_safety_critical_severity() {
    let result = SafetyScanResult {
        safe: false,
        threats: vec![ThreatFinding {
            rule: SafetyRule::MultiStatement,
            severity: SafetySeverity::Critical,
            message: "Multi-statement injection".to_string(),
            detail: "Semicolon followed by DROP TABLE".to_string(),
            location: None,
            matched_pattern: "; DROP TABLE".to_string(),
        }],
        risk_score: 1.0,
        statement_count: 2,
        statement_types: vec!["SELECT".to_string(), "DROP".to_string()],
    };
    let sarif = safety_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results[0].level, "error");
    assert_eq!(
        sarif.runs[0].results[0].rule_id,
        "safety/multi_statement_injection"
    );
}

#[test]
fn sarif_safety_medium_severity_is_warning() {
    let result = SafetyScanResult {
        safe: false,
        threats: vec![ThreatFinding {
            rule: SafetyRule::StringConcatPredicate,
            severity: SafetySeverity::Medium,
            message: "String concatenation in predicate".to_string(),
            detail: "Possible injection via concatenation".to_string(),
            location: None,
            matched_pattern: "||".to_string(),
        }],
        risk_score: 0.5,
        statement_count: 1,
        statement_types: vec!["SELECT".to_string()],
    };
    let sarif = safety_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results[0].level, "warning");
}

#[test]
fn sarif_safety_low_severity_is_note() {
    let result = SafetyScanResult {
        safe: false,
        threats: vec![ThreatFinding {
            rule: SafetyRule::InfoSchemaProbe,
            severity: SafetySeverity::Low,
            message: "Information schema probe".to_string(),
            detail: "Query accesses system catalog".to_string(),
            location: None,
            matched_pattern: "information_schema".to_string(),
        }],
        risk_score: 0.2,
        statement_count: 1,
        statement_types: vec!["SELECT".to_string()],
    };
    let sarif = safety_to_sarif(&result, "test.sql");
    assert_eq!(sarif.runs[0].results[0].level, "note");
}

// ═══════════════════════════════════════════════════════════════════════
// Migration SARIF
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn sarif_migration_safe_zero_results() {
    let result = MigrationResult {
        sql: "ALTER TABLE users ADD COLUMN age INT".to_string(),
        overall_risk: MigrationRisk::Safe,
        findings: vec![],
        safe: true,
        rollback_sql: None,
    };
    let sarif = migration_to_sarif(&result, "migration.sql");
    assert!(sarif.runs[0].results.is_empty());
}

#[test]
fn sarif_migration_drop_column() {
    let result = MigrationResult {
        sql: "ALTER TABLE users DROP COLUMN email".to_string(),
        overall_risk: MigrationRisk::Destructive,
        findings: vec![MigrationFinding {
            risk: MigrationRisk::Destructive,
            operation: "DROP COLUMN".to_string(),
            message: "Dropping column 'email' causes irreversible data loss".to_string(),
            mitigation: Some("Back up data before dropping".to_string()),
            rollback_sql: Some("ALTER TABLE users ADD COLUMN email VARCHAR".to_string()),
        }],
        safe: false,
        rollback_sql: Some("ALTER TABLE users ADD COLUMN email VARCHAR".to_string()),
    };
    let sarif = migration_to_sarif(&result, "migration.sql");
    assert_eq!(sarif.runs[0].results.len(), 1);
    assert_eq!(sarif.runs[0].results[0].rule_id, "migration/drop_column");
    assert_eq!(sarif.runs[0].results[0].level, "error");
}

#[test]
fn sarif_migration_caution_is_warning() {
    let result = MigrationResult {
        sql: "ALTER TABLE users ALTER COLUMN name TYPE TEXT".to_string(),
        overall_risk: MigrationRisk::Caution,
        findings: vec![MigrationFinding {
            risk: MigrationRisk::Caution,
            operation: "ALTER TYPE".to_string(),
            message: "Type change may cause data truncation".to_string(),
            mitigation: None,
            rollback_sql: None,
        }],
        safe: false,
        rollback_sql: None,
    };
    let sarif = migration_to_sarif(&result, "migration.sql");
    assert_eq!(sarif.runs[0].results[0].level, "warning");
}

#[test]
fn sarif_migration_safe_finding_is_note() {
    let result = MigrationResult {
        sql: "ALTER TABLE users ADD COLUMN age INT DEFAULT 0".to_string(),
        overall_risk: MigrationRisk::Safe,
        findings: vec![MigrationFinding {
            risk: MigrationRisk::Safe,
            operation: "ADD COLUMN".to_string(),
            message: "Adding nullable column is safe".to_string(),
            mitigation: None,
            rollback_sql: None,
        }],
        safe: true,
        rollback_sql: None,
    };
    let sarif = migration_to_sarif(&result, "migration.sql");
    assert_eq!(sarif.runs[0].results[0].level, "note");
}

// ═══════════════════════════════════════════════════════════════════════
// SARIF JSON structure
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn sarif_serializes_to_valid_json_with_schema_and_version() {
    let result = ValidationResult::with_errors(vec![ValidationError {
        code: ErrorCode::E000,
        kind: ErrorKind::SyntaxError,
        message: "Unexpected token".to_string(),
        location: None,
        suggestions: vec![],
    }]);
    let sarif = validation_to_sarif(&result, "test.sql");
    let json_str =
        serde_json::to_string_pretty(&sarif).expect("SARIF should serialize to valid JSON");

    // Parse back to verify it's valid JSON
    let parsed: serde_json::Value =
        serde_json::from_str(&json_str).expect("SARIF JSON should parse back");

    assert_eq!(parsed["$schema"], SARIF_SCHEMA_URL);
    assert_eq!(parsed["version"], "2.1.0");
    assert_eq!(parsed["runs"][0]["tool"]["driver"]["name"], "altimate-core");
    assert!(
        parsed["runs"][0]["tool"]["driver"]["version"]
            .as_str()
            .is_some()
    );
}

#[test]
fn sarif_all_types_have_consistent_structure() {
    // Validate SARIF
    let v_result = ValidationResult::ok(Default::default());
    let v_sarif = validation_to_sarif(&v_result, "a.sql");
    let v_json: serde_json::Value = serde_json::to_value(&v_sarif).unwrap();
    assert!(v_json["$schema"].is_string());
    assert!(v_json["runs"].is_array());

    // Lint SARIF
    let l_result = LintResult {
        sql: String::new(),
        findings: vec![],
        error_count: 0,
        warning_count: 0,
        clean: true,
    };
    let l_sarif = lint_to_sarif(&l_result, "a.sql");
    let l_json: serde_json::Value = serde_json::to_value(&l_sarif).unwrap();
    assert_eq!(l_json["version"], "2.1.0");

    // Safety SARIF
    let s_result = SafetyScanResult {
        safe: true,
        threats: vec![],
        risk_score: 0.0,
        statement_count: 0,
        statement_types: vec![],
    };
    let s_sarif = safety_to_sarif(&s_result, "a.sql");
    let s_json: serde_json::Value = serde_json::to_value(&s_sarif).unwrap();
    assert_eq!(s_json["version"], "2.1.0");

    // Migration SARIF
    let m_result = MigrationResult {
        sql: String::new(),
        overall_risk: MigrationRisk::Safe,
        findings: vec![],
        safe: true,
        rollback_sql: None,
    };
    let m_sarif = migration_to_sarif(&m_result, "a.sql");
    let m_json: serde_json::Value = serde_json::to_value(&m_sarif).unwrap();
    assert_eq!(m_json["version"], "2.1.0");
}

// ═══════════════════════════════════════════════════════════════════════
// End-to-end: real validator + SARIF
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn sarif_validate_real_invalid_sql() {
    use altimate_core::schema::types::*;
    use std::collections::HashMap;

    let mut tables = HashMap::new();
    tables.insert(
        "users".to_string(),
        TableDef {
            description: None,
            database: None,
            schema: None,
            columns: vec![ColumnDef {
                name: "id".to_string(),
                data_type: "INT".to_string(),
                nullable: false,
                description: None,
                tags: vec![],
                synonyms: vec![],
                statistics: None,
            }],
            primary_key: None,
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );
    let schema = SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables,
        database: None,
        schema_name: None,
        glossary: None,
    };

    // Query references a non-existent table
    let result = altimate_core::validate("SELECT id FROM nonexistent", &schema).await;
    let sarif = validation_to_sarif(&result, "test.sql");
    assert!(!sarif.runs[0].results.is_empty());
    // Should have at least one error result
    assert!(sarif.runs[0].results.iter().all(|r| r.level == "error"));
}

#[test]
fn sarif_lint_real_anti_pattern() {
    use altimate_core::schema::types::*;
    use std::collections::HashMap;

    let schema = SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables: HashMap::new(),
        database: None,
        schema_name: None,
        glossary: None,
    };

    let result = altimate_core::linter::lint("SELECT * FROM users", &schema);
    let sarif = lint_to_sarif(&result, "test.sql");
    // Should detect L001 (SELECT *)
    assert!(!sarif.runs[0].results.is_empty());
    assert!(sarif.runs[0].results.iter().any(|r| r.rule_id == "L001"));
}

#[test]
fn sarif_safety_real_injection() {
    let config = altimate_core::safety::SafetyConfig::default();
    let result =
        altimate_core::safety::scan_sql("SELECT * FROM users WHERE id = 1 OR 1=1", &config);
    let sarif = safety_to_sarif(&result, "test.sql");
    assert!(!sarif.runs[0].results.is_empty());
}

#[test]
fn sarif_migration_real_drop() {
    use altimate_core::schema::types::*;
    use std::collections::HashMap;

    let schema = SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables: HashMap::new(),
        database: None,
        schema_name: None,
        glossary: None,
    };

    let result = altimate_core::migration::analyze_migration("DROP TABLE users;", &schema);
    let sarif = migration_to_sarif(&result, "migration.sql");
    assert!(!sarif.runs[0].results.is_empty());
    // At least one error-level result for destructive operation
    assert!(sarif.runs[0].results.iter().any(|r| r.level == "error"));
}
