//! Integration tests for the tenant-aware feedback calibration system.

use altimate_core::complexity::CostProfile;
use altimate_core::complexity::feedback::*;
use altimate_core::complexity::types::*;
use altimate_core::schema::types::SqlDialect;

fn test_config() -> TenantConfig {
    TenantConfig {
        tenant_id: "integration-test".to_string(),
        name: "Integration Test Tenant".to_string(),
        dialect: SqlDialect::Snowflake,
        created_at: "2026-01-01T00:00:00Z".to_string(),
        feedback_alpha: 0.7,
        max_entries: 1000,
        validation_threshold: 0.10,
    }
}

fn make_entry(fingerprint: &str, actual: u64, estimated: u64, ts: &str) -> FeedbackEntry {
    FeedbackEntry {
        sql_fingerprint: fingerprint.to_string(),
        actual_bytes_scanned: actual,
        estimated_bytes_scanned: estimated,
        calibration_ratio: actual as f64 / estimated as f64,
        recorded_at: ts.to_string(),
    }
}

fn make_cost_estimate(bytes: u64) -> CostEstimate {
    let profile = CostProfile::for_dialect(SqlDialect::Snowflake);
    CostEstimate {
        bytes_scanned: bytes,
        cost_usd: profile.calculate_cost(bytes),
        cost_tier: CostTier::from_cost(profile.calculate_cost(bytes)),
        table_breakdown: vec![],
        warnings: vec![],
        disclaimer: None,
        cost_range: None,
        recommendations: vec![],
        confidence: EstimationConfidence::High,
        confidence_factors: vec![],
        estimation_method: None,
        time_based_cost: None,
    }
}

#[test]
fn test_create_tenant_empty_store() {
    let store = create_tenant(test_config());
    assert!(store.entries.is_empty());
    assert!(store.baseline_accuracy.is_none());
    assert!(store.current_accuracy.is_none());
    assert_eq!(store.tenant.tenant_id, "integration-test");
    assert_eq!(store.tenant.feedback_alpha, 0.7);
    assert_eq!(store.tenant.max_entries, 1000);
}

#[test]
fn test_sql_fingerprint_normalizes_literals() {
    // Numeric literals
    let fp1 = sql_fingerprint("SELECT * FROM users WHERE id = 123");
    let fp2 = sql_fingerprint("SELECT * FROM users WHERE id = 456");
    assert_eq!(fp1, fp2);

    // String literals
    let fp3 = sql_fingerprint("SELECT * FROM users WHERE name = 'alice'");
    let fp4 = sql_fingerprint("SELECT * FROM users WHERE name = 'bob'");
    assert_eq!(fp3, fp4);

    // Mixed
    let fp5 = sql_fingerprint("SELECT * FROM t WHERE a = 1 AND b = 'x'");
    let fp6 = sql_fingerprint("SELECT * FROM t WHERE a = 999 AND b = 'yyy'");
    assert_eq!(fp5, fp6);
}

#[test]
fn test_sql_fingerprint_different_tables_differ() {
    let fp1 = sql_fingerprint("SELECT * FROM users WHERE id = 1");
    let fp2 = sql_fingerprint("SELECT * FROM orders WHERE id = 1");
    assert_ne!(fp1, fp2);
}

#[test]
fn test_calibrate_no_feedback_returns_original() {
    let store = create_tenant(test_config());
    let estimate = make_cost_estimate(1_000_000);

    let result = calibrate_estimate(
        &estimate,
        &store,
        "SELECT * FROM users",
        SqlDialect::Snowflake,
    );

    assert_eq!(result.original_bytes_scanned, 1_000_000);
    assert_eq!(result.calibrated_bytes_scanned, 1_000_000);
    assert_eq!(result.calibration_source, CalibrationSource::NoFeedback);
    assert_eq!(result.feedback_entries_matched, 0);
    assert!((result.original_cost_usd - result.calibrated_cost_usd).abs() < f64::EPSILON);
}

#[test]
fn test_calibrate_with_exact_match() {
    let mut store = create_tenant(test_config());
    let sql = "SELECT * FROM users WHERE id = 1";
    let fp = sql_fingerprint(sql);

    // Actual was 2x the estimate
    store.entries.push(make_entry(
        &fp,
        2_000_000,
        1_000_000,
        "2026-01-01T00:00:00Z",
    ));

    let estimate = make_cost_estimate(1_000_000);
    let result = calibrate_estimate(&estimate, &store, sql, SqlDialect::Snowflake);

    assert_eq!(result.calibration_source, CalibrationSource::ExactMatch);
    assert_eq!(result.feedback_entries_matched, 1);
    // With alpha=0.7: calibrated = 1M * (0.7 + 0.3 * 2.0) = 1M * 1.3 = 1.3M
    assert_eq!(result.calibrated_bytes_scanned, 1_300_000);
    assert!(result.calibrated_cost_usd > result.original_cost_usd);
}

#[test]
fn test_calibrate_blends_with_alpha() {
    let mut config = test_config();
    config.feedback_alpha = 0.5; // 50/50 blend
    let mut store = create_tenant(config);
    let sql = "SELECT * FROM users WHERE id = 1";
    let fp = sql_fingerprint(sql);

    // Actual was 3x the estimate
    store.entries.push(make_entry(
        &fp,
        3_000_000,
        1_000_000,
        "2026-01-01T00:00:00Z",
    ));

    let estimate = make_cost_estimate(1_000_000);
    let result = calibrate_estimate(&estimate, &store, sql, SqlDialect::Snowflake);

    // With alpha=0.5: calibrated = 1M * (0.5 + 0.5 * 3.0) = 1M * 2.0 = 2M
    assert_eq!(result.calibrated_bytes_scanned, 2_000_000);
}

#[test]
fn test_ingest_single_entry() {
    let mut store = create_tenant(test_config());
    let entry = make_entry("fp1", 1_000_000, 1_000_000, "2026-01-01T00:00:00Z");

    let result = ingest_feedback(&mut store, vec![entry]);

    assert_eq!(result.new_entries_added, 1);
    assert_eq!(result.total_entries, 1);
    assert!(!result.regression_detected);
    assert!(!result.rolled_back);
    assert!(store.baseline_accuracy.is_some());
    assert!(store.current_accuracy.is_some());
}

#[test]
fn test_ingest_batch_deduplicates() {
    let mut store = create_tenant(test_config());

    let entries = vec![
        make_entry("fp1", 1_000_000, 1_000_000, "2026-01-01T00:00:00Z"),
        make_entry("fp1", 1_200_000, 1_000_000, "2026-01-02T00:00:00Z"),
        make_entry("fp2", 2_000_000, 2_000_000, "2026-01-01T00:00:00Z"),
    ];

    let result = ingest_feedback(&mut store, entries);

    assert_eq!(result.new_entries_added, 3);
    // After dedup: 2 unique fingerprints
    assert_eq!(store.entries.len(), 2);
}

#[test]
fn test_ingest_respects_max_entries() {
    let mut config = test_config();
    config.max_entries = 5;
    let mut store = create_tenant(config);

    let entries: Vec<FeedbackEntry> = (0..10)
        .map(|i| {
            make_entry(
                &format!("fp{i}"),
                1_000_000,
                1_000_000,
                &format!("2026-01-{:02}T00:00:00Z", i + 1),
            )
        })
        .collect();

    ingest_feedback(&mut store, entries);

    assert_eq!(store.entries.len(), 5);
}

#[test]
fn test_regression_detection_rolls_back() {
    let mut store = create_tenant(test_config());

    // Good feedback: all within 10%
    let good: Vec<FeedbackEntry> = (0..10)
        .map(|i| {
            make_entry(
                &format!("good{i}"),
                1_000_000,
                1_000_000,
                &format!("2026-01-{:02}T00:00:00Z", i + 1),
            )
        })
        .collect();
    ingest_feedback(&mut store, good);
    let count_before = store.entries.len();

    // Bad feedback: 10x off
    let bad: Vec<FeedbackEntry> = (0..20)
        .map(|i| {
            make_entry(
                &format!("bad{i}"),
                10_000_000,
                1_000_000,
                &format!("2026-02-{:02}T00:00:00Z", i + 1),
            )
        })
        .collect();

    let result = ingest_feedback(&mut store, bad);

    assert!(result.regression_detected);
    assert!(result.rolled_back);
    assert_eq!(store.entries.len(), count_before);
}

#[test]
fn test_regression_within_threshold_accepted() {
    let mut store = create_tenant(test_config());

    // First: perfect accuracy
    let good: Vec<FeedbackEntry> = (0..10)
        .map(|i| {
            make_entry(
                &format!("good{i}"),
                1_000_000,
                1_000_000,
                &format!("2026-01-{:02}T00:00:00Z", i + 1),
            )
        })
        .collect();
    ingest_feedback(&mut store, good);

    // Slightly off: within 10% (ratio ~1.05)
    let ok: Vec<FeedbackEntry> = (0..5)
        .map(|i| {
            make_entry(
                &format!("ok{i}"),
                1_050_000,
                1_000_000,
                &format!("2026-02-{:02}T00:00:00Z", i + 1),
            )
        })
        .collect();

    let result = ingest_feedback(&mut store, ok);

    assert!(!result.regression_detected);
    assert!(!result.rolled_back);
    assert_eq!(store.entries.len(), 15);
}

#[test]
fn test_incremental_ingestion_accumulates() {
    let mut store = create_tenant(test_config());

    for i in 0..3 {
        let batch = vec![make_entry(
            &format!("fp{i}"),
            1_000_000,
            1_000_000,
            &format!("2026-01-0{}T00:00:00Z", i + 1),
        )];
        ingest_feedback(&mut store, batch);
    }

    assert_eq!(store.entries.len(), 3);
}

#[test]
fn test_feedback_store_save_load_roundtrip() {
    let tenant_id = format!("integration-roundtrip-{}", std::process::id());
    let mut config = test_config();
    config.tenant_id = tenant_id.clone();

    let mut store = create_tenant(config);
    store.entries.push(make_entry(
        "fp1",
        1_000_000,
        900_000,
        "2026-01-01T00:00:00Z",
    ));
    store.entries.push(make_entry(
        "fp2",
        2_000_000,
        2_100_000,
        "2026-01-02T00:00:00Z",
    ));

    // Ingest to set accuracy baselines
    let entries = store.entries.clone();
    store.entries.clear();
    ingest_feedback(&mut store, entries);

    let save_result = save_feedback_store(&store);
    assert!(save_result.is_ok(), "Save failed: {:?}", save_result.err());

    let loaded = load_feedback_store(&tenant_id);
    assert!(loaded.is_ok(), "Load failed: {:?}", loaded.err());
    let loaded = loaded.unwrap();

    assert_eq!(loaded.tenant.tenant_id, tenant_id);
    assert_eq!(loaded.entries.len(), 2);
    assert!(loaded.baseline_accuracy.is_some());
    assert!(loaded.current_accuracy.is_some());

    // Cleanup
    let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
    let dir = std::path::PathBuf::from(home)
        .join(".altimate")
        .join("tenants")
        .join(&tenant_id);
    let _ = std::fs::remove_dir_all(&dir);
}

#[test]
fn test_tenant_status_reports_correctly() {
    let mut store = create_tenant(test_config());

    store.entries.push(make_entry(
        "fp1",
        1_000_000,
        1_000_000,
        "2026-01-01T00:00:00Z",
    ));
    store.entries.push(make_entry(
        "fp2",
        2_000_000,
        2_000_000,
        "2026-01-02T00:00:00Z",
    ));
    store.entries.push(make_entry(
        "fp1",
        1_100_000,
        1_000_000,
        "2026-01-03T00:00:00Z",
    ));

    let status = tenant_status(&store);

    assert_eq!(status.tenant_id, "integration-test");
    assert_eq!(status.name, "Integration Test Tenant");
    assert_eq!(status.total_entries, 3);
    assert_eq!(status.unique_fingerprints, 2);
}

#[test]
fn test_calibration_cost_consistency() {
    // Verify that calibrated cost is consistent with calibrated bytes
    let mut store = create_tenant(test_config());
    let sql = "SELECT * FROM users WHERE id = 1";
    let fp = sql_fingerprint(sql);

    store
        .entries
        .push(make_entry(&fp, 500_000, 1_000_000, "2026-01-01T00:00:00Z"));

    let estimate = make_cost_estimate(1_000_000);
    let result = calibrate_estimate(&estimate, &store, sql, SqlDialect::Snowflake);

    // Verify the calibrated cost matches what CostProfile.calculate_cost() returns
    let profile = CostProfile::for_dialect(SqlDialect::Snowflake);
    let expected_cost = profile.calculate_cost(result.calibrated_bytes_scanned);
    assert!(
        (result.calibrated_cost_usd - expected_cost).abs() < 1e-10,
        "Calibrated cost {} should match profile.calculate_cost({}) = {}",
        result.calibrated_cost_usd,
        result.calibrated_bytes_scanned,
        expected_cost
    );
}

#[test]
fn test_multiple_feedback_entries_average_ratio() {
    let mut store = create_tenant(test_config());
    let sql = "SELECT * FROM users WHERE id = 1";
    let fp = sql_fingerprint(sql);

    // Two entries: one 2x, one 0.5x -> avg ratio = 1.25
    store.entries.push(make_entry(
        &fp,
        2_000_000,
        1_000_000,
        "2026-01-01T00:00:00Z",
    ));
    store
        .entries
        .push(make_entry(&fp, 500_000, 1_000_000, "2026-01-02T00:00:00Z"));

    let estimate = make_cost_estimate(1_000_000);
    let result = calibrate_estimate(&estimate, &store, sql, SqlDialect::Snowflake);

    // avg_ratio = (2.0 + 0.5) / 2 = 1.25
    // calibrated = 1M * (0.7 + 0.3 * 1.25) = 1M * 1.075 = 1,075,000
    assert_eq!(result.calibrated_bytes_scanned, 1_075_000);
    assert_eq!(result.feedback_entries_matched, 2);
}

#[test]
fn test_feedback_serde_roundtrip() {
    let store = create_tenant(test_config());
    let json = serde_json::to_string_pretty(&store).unwrap();
    let deserialized: FeedbackStore = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.tenant.tenant_id, "integration-test");
    assert!(deserialized.entries.is_empty());
}

#[test]
fn test_calibrated_estimate_serde() {
    let result = CalibratedEstimate {
        original_bytes_scanned: 1_000_000,
        calibrated_bytes_scanned: 1_300_000,
        original_cost_usd: 0.001,
        calibrated_cost_usd: 0.0013,
        calibration_source: CalibrationSource::ExactMatch,
        feedback_entries_matched: 1,
    };
    let json = serde_json::to_string(&result).unwrap();
    assert!(json.contains("\"exact_match\""));
    let deserialized: CalibratedEstimate = serde_json::from_str(&json).unwrap();
    assert_eq!(
        deserialized.calibrated_bytes_scanned,
        result.calibrated_bytes_scanned
    );
}
