// SPDX-License-Identifier: GPL-3.0-or-later
//! Hardware-in-the-loop DDC tests — READ ONLY.
//!
//! These tests exercise the real FFI code path with physical monitors.
//! They ONLY read brightness values — no writes, no VCP codes besides 0x10.
//!
//! Requirements:
//! - `cargo test --features hw-test` to enable
//! - User must be in `i2c` group (or root)
//! - At least one DDC/CI monitor connected
//!
//! Safety guarantees:
//! - Tests only call ddca_get_non_table_vcp_value (read)
//! - No calls to ddca_set_non_table_vcp_value (write)
//! - All tests skip gracefully if no monitors found

#![cfg(feature = "hw-test")]

use temperlux::display::ddc_ffi::{detect_displays, DdcService};

// ─── Detection tests ────────────────────────────────────────────────

#[test]
fn hw_detect_finds_at_least_one_display() {
    let handles = match detect_displays() {
        Ok(h) => h,
        Err(e) => {
            eprintln!("SKIP: detect_displays failed: {e}");
            return;
        }
    };
    if handles.is_empty() {
        eprintln!("SKIP: No DDC displays detected");
        return;
    }
    assert!(!handles.is_empty(), "Should detect at least one display");
}

#[test]
fn hw_detect_returns_valid_model_names() {
    let handles = match detect_displays() {
        Ok(h) => h,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if handles.is_empty() { return; }
    for h in &handles {
        assert!(!h.model_name.is_empty(), "Display {} should have a model name", h.display_num);
    }
}

#[test]
fn hw_detect_display_numbers_are_positive() {
    let handles = match detect_displays() {
        Ok(h) => h,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if handles.is_empty() { return; }
    for h in &handles {
        assert!(h.display_num > 0, "Display num should be positive, got {}", h.display_num);
    }
}

#[test]
fn hw_detect_max_brightness_is_nonzero() {
    let handles = match detect_displays() {
        Ok(h) => h,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if handles.is_empty() { return; }
    for h in &handles {
        assert!(h.max_val > 0, "Display {} should have max_val > 0", h.display_num);
    }
}

#[test]
fn hw_detect_current_brightness_within_range() {
    let handles = match detect_displays() {
        Ok(h) => h,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if handles.is_empty() { return; }
    for h in &handles {
        assert!(
            h.last_val <= h.max_val,
            "Display {} current {} exceeds max {}",
            h.display_num, h.last_val, h.max_val
        );
    }
}

// ─── Read brightness tests ──────────────────────────────────────────

#[test]
fn hw_get_brightness_returns_valid_value() {
    let mut handles = match detect_displays() {
        Ok(h) => h,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if handles.is_empty() { return; }
    for h in &mut handles {
        let val = h.get_brightness().expect("get_brightness should succeed");
        assert!(val <= h.max_val, "Brightness {} exceeds max {}", val, h.max_val);
    }
}

#[test]
fn hw_brightness_pct_is_0_to_100() {
    let mut handles = match detect_displays() {
        Ok(h) => h,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if handles.is_empty() { return; }
    for h in &mut handles {
        let pct = h.brightness_pct().expect("brightness_pct should succeed");
        assert!(pct >= 0.0 && pct <= 100.0, "Pct {} out of range", pct);
    }
}

#[test]
fn hw_get_brightness_is_consistent() {
    let mut handles = match detect_displays() {
        Ok(h) => h,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if handles.is_empty() { return; }
    // Two rapid reads should return the same value (no one changed brightness)
    for h in &mut handles {
        let val1 = h.get_brightness().expect("first read should succeed");
        let val2 = h.get_brightness().expect("second read should succeed");
        assert_eq!(val1, val2, "Two consecutive reads should match for display {}", h.display_num);
    }
}

// ─── DdcService tests ───────────────────────────────────────────────

#[test]
fn hw_ddc_service_starts_and_detects() {
    let (mut service, info) = match DdcService::start() {
        Ok(s) => s,
        Err(e) => {
            eprintln!("SKIP: DdcService::start failed: {e}");
            return;
        }
    };
    if info.is_empty() {
        eprintln!("SKIP: No displays");
        service.shutdown();
        return;
    }
    assert!(!info.is_empty(), "Should detect displays via service");
    for d in &info {
        assert!(d.display_num > 0);
        assert!(!d.model_name.is_empty());
        assert!(d.max_brightness > 0);
    }
    service.shutdown();
}

#[test]
fn hw_ddc_service_error_map_starts_empty() {
    let (mut service, info) = match DdcService::start() {
        Ok(s) => s,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if info.is_empty() { service.shutdown(); return; }
    for d in &info {
        assert!(service.get_error(d.display_num).is_none(), "No errors on startup");
    }
    service.shutdown();
}

#[test]
fn hw_ddc_service_latency_starts_zero() {
    let (mut service, info) = match DdcService::start() {
        Ok(s) => s,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    if info.is_empty() { service.shutdown(); return; }
    for d in &info {
        assert_eq!(service.get_latency_ms(d.display_num), 0, "Latency should be 0 before any write");
    }
    service.shutdown();
}

// ─── Error map auto-expiry test (pure logic, no hardware needed) ────

#[test]
fn hw_error_map_auto_expires_after_5_seconds() {
    // This tests the DdcService error map expiry logic directly
    // by creating a service and manipulating the error map
    let (mut service, _) = match DdcService::start() {
        Ok(s) => s,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    // Insert an error with a timestamp 6 seconds ago (expired)
    {
        let mut map = service.errors.lock().unwrap();
        map.insert(99, ("test error".to_string(), std::time::Instant::now() - std::time::Duration::from_secs(6)));
    }
    // get_error should return None (expired)
    assert!(service.get_error(99).is_none(), "Error should have auto-expired");
    service.shutdown();
}

#[test]
fn hw_error_map_returns_recent_error() {
    let (mut service, _) = match DdcService::start() {
        Ok(s) => s,
        Err(_) => { eprintln!("SKIP"); return; }
    };
    // Insert a fresh error
    {
        let mut map = service.errors.lock().unwrap();
        map.insert(99, ("test error".to_string(), std::time::Instant::now()));
    }
    assert_eq!(service.get_error(99).unwrap(), "test error", "Fresh error should be returned");
    service.shutdown();
}
