//! Integration tests for the query observability module.
//!
//! Tests cover evaluation across quality dimensions, grade assignment,
//! scorecard computation, config variants, observability events, and
//! serialization.

use altimate_core::observability::engine::{create_event, evaluate, evaluate_default};
use altimate_core::observability::types::{EvalConfig, Grade, QualityScorecard};
use altimate_core::schema::types::SchemaDefinition;

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

// ═══════════════════════════════════════════════════════════════════════
// Basic evaluation — valid queries
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_valid_simple_select() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert!(result.scores.syntax >= 1.0);
    assert_eq!(result.overall_grade, Grade::A);
}

#[tokio::test]
async fn observability_valid_select_star() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT * FROM users", &schema).await;
    assert!(result.scores.syntax >= 1.0);
    // SELECT * triggers lint warnings, so style may be < 1.0
    assert!(result.scores.style <= 1.0);
}

#[tokio::test]
async fn observability_valid_join_query() {
    let schema = ecommerce_schema();
    let result = evaluate_default(
        "SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id",
        &schema,
    )
    .await;
    assert!(result.scores.syntax >= 1.0);
    // Join increases complexity
    assert!(result.scores.complexity <= 1.0);
}

#[tokio::test]
async fn observability_valid_aggregation() {
    let schema = ecommerce_schema();
    let result = evaluate_default(
        "SELECT user_id, SUM(total_amount) FROM orders GROUP BY user_id",
        &schema,
    )
    .await;
    assert!(result.scores.syntax >= 1.0);
}

#[tokio::test]
async fn observability_valid_multi_join() {
    let schema = ecommerce_schema();
    let result = evaluate_default(
        "SELECT u.name, p.name FROM users u \
         JOIN orders o ON u.id = o.user_id \
         JOIN order_items oi ON o.id = oi.order_id \
         JOIN products p ON oi.product_id = p.id",
        &schema,
    )
    .await;
    assert!(result.scores.syntax >= 1.0);
    // Multi-join should lower complexity score
    assert!(result.scores.complexity < 1.0);
}

// ═══════════════════════════════════════════════════════════════════════
// Invalid SQL evaluation
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_invalid_sql_low_syntax() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECTZ * FROM users", &schema).await;
    assert!(result.scores.syntax < 1.0);
    assert_ne!(result.overall_grade, Grade::A);
}

#[tokio::test]
async fn observability_empty_sql() {
    let schema = ecommerce_schema();
    let result = evaluate_default("", &schema).await;
    assert!(result.scores.syntax < 1.0);
}

#[tokio::test]
async fn observability_invalid_table() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT * FROM nonexistent_table", &schema).await;
    assert!(result.scores.syntax < 1.0);
}

// ═══════════════════════════════════════════════════════════════════════
// Grade assignment
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn observability_grade_from_score_a() {
    assert_eq!(Grade::from_score(1.0), Grade::A);
    assert_eq!(Grade::from_score(0.95), Grade::A);
    assert_eq!(Grade::from_score(0.9), Grade::A);
}

#[test]
fn observability_grade_from_score_b() {
    assert_eq!(Grade::from_score(0.89), Grade::B);
    assert_eq!(Grade::from_score(0.8), Grade::B);
}

#[test]
fn observability_grade_from_score_c() {
    assert_eq!(Grade::from_score(0.79), Grade::C);
    assert_eq!(Grade::from_score(0.7), Grade::C);
}

#[test]
fn observability_grade_from_score_d() {
    assert_eq!(Grade::from_score(0.69), Grade::D);
    assert_eq!(Grade::from_score(0.5), Grade::D);
}

#[test]
fn observability_grade_from_score_f() {
    assert_eq!(Grade::from_score(0.49), Grade::F);
    assert_eq!(Grade::from_score(0.0), Grade::F);
}

#[test]
fn observability_grade_display() {
    assert_eq!(Grade::A.to_string(), "A");
    assert_eq!(Grade::B.to_string(), "B");
    assert_eq!(Grade::C.to_string(), "C");
    assert_eq!(Grade::D.to_string(), "D");
    assert_eq!(Grade::F.to_string(), "F");
}

// ═══════════════════════════════════════════════════════════════════════
// Scorecard computation
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn observability_scorecard_perfect() {
    let mut s = QualityScorecard {
        syntax: 1.0,
        style: 1.0,
        safety: 1.0,
        complexity: 1.0,
        overall: 0.0,
    };
    s.compute_overall();
    assert!((s.overall - 1.0).abs() < f64::EPSILON);
}

#[test]
fn observability_scorecard_zero() {
    let mut s = QualityScorecard {
        syntax: 0.0,
        style: 0.0,
        safety: 0.0,
        complexity: 0.0,
        overall: 0.0,
    };
    s.compute_overall();
    assert!((s.overall - 0.0).abs() < f64::EPSILON);
}

#[test]
fn observability_scorecard_weighted_computation() {
    let mut s = QualityScorecard {
        syntax: 1.0,
        style: 0.5,
        safety: 0.8,
        complexity: 0.6,
        overall: 0.0,
    };
    s.compute_overall();
    // 1.0*0.3 + 0.5*0.2 + 0.8*0.2 + 0.6*0.3 = 0.3 + 0.1 + 0.16 + 0.18 = 0.74
    assert!((s.overall - 0.74).abs() < 0.001);
}

#[test]
fn observability_scorecard_overall_range() {
    let mut s = QualityScorecard {
        syntax: 0.7,
        style: 0.3,
        safety: 0.9,
        complexity: 0.4,
        overall: 0.0,
    };
    s.compute_overall();
    assert!(s.overall >= 0.0);
    assert!(s.overall <= 1.0);
}

// ═══════════════════════════════════════════════════════════════════════
// Config variants
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_validation_disabled() {
    let schema = ecommerce_schema();
    let config = EvalConfig {
        enable_validation: false,
        ..Default::default()
    };
    let result = evaluate("SELECTZ broken", &schema, &config).await;
    // Validation disabled -> syntax assumed valid
    assert!((result.scores.syntax - 1.0).abs() < f64::EPSILON);
    assert!(result.validation.is_none());
}

#[tokio::test]
async fn observability_lint_disabled() {
    let schema = ecommerce_schema();
    let config = EvalConfig {
        enable_lint: false,
        ..Default::default()
    };
    let result = evaluate("SELECT * FROM users", &schema, &config).await;
    assert!((result.scores.style - 1.0).abs() < f64::EPSILON);
    assert!(result.lint.is_none());
}

#[tokio::test]
async fn observability_explain_disabled() {
    let schema = ecommerce_schema();
    let config = EvalConfig {
        enable_explain: false,
        ..Default::default()
    };
    let result = evaluate("SELECT id FROM users", &schema, &config).await;
    assert!((result.scores.complexity - 1.0).abs() < f64::EPSILON);
    assert!(result.explain.is_none());
}

#[tokio::test]
async fn observability_all_disabled() {
    let schema = ecommerce_schema();
    let config = EvalConfig {
        enable_validation: false,
        enable_lint: false,
        enable_safety: false,
        enable_explain: false,
    };
    let result = evaluate("anything", &schema, &config).await;
    assert!(result.scores.overall > 0.5);
}

// ═══════════════════════════════════════════════════════════════════════
// Safety scoring
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_safe_query_high_safety_score() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert!((result.scores.safety - 1.0).abs() < f64::EPSILON);
}

#[tokio::test]
async fn observability_drop_table_low_safety() {
    let schema = ecommerce_schema();
    let result = evaluate_default("DROP TABLE users", &schema).await;
    assert!(result.scores.safety < 0.6);
}

// ═══════════════════════════════════════════════════════════════════════
// Observability events
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_event_basic_fields() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    let event = create_event("SELECT id FROM users", &result);
    assert_eq!(event.event_type, "sql_evaluation");
    assert!(!event.timestamp.is_empty());
    assert_eq!(event.sql, "SELECT id FROM users");
}

#[tokio::test]
async fn observability_event_grade() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    let event = create_event("SELECT id FROM users", &result);
    assert_eq!(event.grade, Grade::A);
}

#[tokio::test]
async fn observability_event_has_metadata() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    let event = create_event("SELECT id FROM users", &result);
    assert!(event.metadata.contains_key("syntax_valid"));
    assert!(event.metadata.contains_key("grade"));
}

#[tokio::test]
async fn observability_event_invalid_sql() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECTZ broken", &schema).await;
    let event = create_event("SELECTZ broken", &result);
    assert!(event.error_count > 0 || result.scores.syntax < 1.0);
}

#[tokio::test]
async fn observability_event_duration_populated() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    let event = create_event("SELECT id FROM users", &result);
    // duration should be populated (>= 0)
    assert!(event.duration_ms < 10000);
}

// ═══════════════════════════════════════════════════════════════════════
// Time tracking
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_eval_tracks_time() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert!(result.total_time_ms < 5000);
}

// ═══════════════════════════════════════════════════════════════════════
// Serialization
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_eval_result_serializes() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("overall_grade"));
    assert!(json.contains("scores"));
    assert!(json.contains("sql"));
}

#[test]
fn observability_eval_config_serializes() {
    let config = EvalConfig::default();
    let json = serde_json::to_string(&config).expect("should serialize");
    assert!(json.contains("enable_validation"));
    assert!(json.contains("enable_lint"));
}

#[tokio::test]
async fn observability_event_serializes() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    let event = create_event("SELECT id FROM users", &result);
    let json = serde_json::to_string(&event).expect("should serialize");
    assert!(json.contains("event_type"));
    assert!(json.contains("timestamp"));
    assert!(json.contains("scores"));
}

#[test]
fn observability_scorecard_serializes() {
    let mut s = QualityScorecard {
        syntax: 1.0,
        style: 0.8,
        safety: 0.9,
        complexity: 0.7,
        overall: 0.0,
    };
    s.compute_overall();
    let json = serde_json::to_string(&s).expect("should serialize");
    assert!(json.contains("overall"));
    assert!(json.contains("syntax"));
}

// ═══════════════════════════════════════════════════════════════════════
// EvalResult field access
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn observability_result_has_sql_field() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert_eq!(result.sql, "SELECT id FROM users");
}

#[tokio::test]
async fn observability_result_validation_present() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert!(result.validation.is_some());
}

#[tokio::test]
async fn observability_result_lint_present() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert!(result.lint.is_some());
}

#[tokio::test]
async fn observability_result_safety_present() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert!(result.safety.is_some());
}

#[tokio::test]
async fn observability_result_explain_present() {
    let schema = ecommerce_schema();
    let result = evaluate_default("SELECT id FROM users", &schema).await;
    assert!(result.explain.is_some());
}
