// SPDX-License-Identifier: GPL-3.0-or-later
//! Display manager tests using mock providers.

mod provider_mock;

use temperlux::display::ddc_ffi::DdcDisplayInfo;
use temperlux::display::manager::DisplayManager;
use temperlux::display::provider::BacklightDisplayInfo;
use temperlux::settings::Settings;
use provider_mock::*;

fn ddc_display(name: &str, current: u16, max: u16) -> DdcDisplayInfo {
    DdcDisplayInfo { display_num: 1, model_name: name.to_string(), current_brightness: current, max_brightness: max }
}

fn ddc_display_n(num: i32, name: &str, current: u16, max: u16) -> DdcDisplayInfo {
    DdcDisplayInfo { display_num: num, model_name: name.to_string(), current_brightness: current, max_brightness: max }
}

fn bl_display(name: &str, current: u32, max: u32) -> BacklightDisplayInfo {
    BacklightDisplayInfo { name: name.to_string(), current, max }
}

// P1: --list
#[test]
fn list_shows_all_displays() {
    let ffi_displays = vec![
        ddc_display_n(1, "Dell U2723QE", 50, 100),
        ddc_display_n(2, "LG 27UK850", 30, 100),
    ];
    let bl = MockBacklightProvider::new(vec![]);
    let settings = Settings::default();
    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    assert_eq!(mgr.count(), 2);
    let displays = mgr.displays();
    assert_eq!(displays[0].name, "Dell U2723QE");
    assert!((displays[0].brightness_pct - 50.0).abs() < f64::EPSILON);
    assert_eq!(displays[1].name, "LG 27UK850");
    assert!((displays[1].brightness_pct - 30.0).abs() < f64::EPSILON);
}

#[test]
fn list_no_displays() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![]);
    let settings = Settings::default();
    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    assert_eq!(mgr.count(), 0);
}

#[test]
fn list_mixed_ddc_and_backlight() {
    let ffi_displays = vec![ddc_display_n(1, "External", 75, 100)];
    let bl = MockBacklightProvider::new(vec![bl_display("amdgpu_bl0", 50, 100)]);
    let settings = Settings::default();
    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    assert_eq!(mgr.count(), 2);
    let displays = mgr.displays();
    // Backlight first
    assert_eq!(displays[0].index, 1);
    assert!(matches!(displays[0].display_type, temperlux::display::DisplayType::Backlight));
    assert_eq!(displays[1].index, 2);
    assert!(matches!(displays[1].display_type, temperlux::display::DisplayType::Ddc));
}

// P2: --get
#[test]
fn get_brightness_of_display() {
    let ffi_displays = vec![ddc_display("Monitor", 75, 100)];
    let bl = MockBacklightProvider::new(vec![]);
    let settings = Settings::default();
    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    let displays = mgr.displays();
    assert!((displays[0].brightness_pct - 75.0).abs() < f64::EPSILON);
}

#[test]
fn get_invalid_display_returns_error() {
    let ffi_displays = vec![ddc_display("Monitor", 50, 100)];
    let bl = MockBacklightProvider::new(vec![]);
    let settings = Settings::default();
    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    // get() prints and returns, but for invalid index it should error
    let result = mgr.get(3);
    assert!(result.is_err());
}

// P3: --set (backlight only — DDC set requires DdcService)
#[test]
fn set_brightness_backlight_display() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![bl_display("amdgpu_bl0", 50, 100)]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.set_with_service(Some(1), 80.0, None).unwrap();
    assert!((mgr.displays()[0].brightness_pct - 80.0).abs() < f64::EPSILON);
}

#[test]
fn increase_brightness_backlight_display() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![bl_display("amdgpu_bl0", 50, 100)]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.increase_with_service(Some(1), 10.0, None).unwrap();
    assert!((mgr.displays()[0].brightness_pct - 60.0).abs() < f64::EPSILON);
}

#[test]
fn decrease_brightness_backlight_display() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![bl_display("amdgpu_bl0", 50, 100)]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.decrease_with_service(Some(1), 10.0, None).unwrap();
    assert!((mgr.displays()[0].brightness_pct - 40.0).abs() < f64::EPSILON);
}

#[test]
fn decrease_clamps_to_zero() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![bl_display("amdgpu_bl0", 5, 100)]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.decrease_with_service(Some(1), 10.0, None).unwrap();
    assert!((mgr.displays()[0].brightness_pct).abs() < f64::EPSILON);
}

#[test]
fn set_all_backlight_displays() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![
        bl_display("bl0", 50, 100),
        bl_display("bl1", 30, 100),
    ]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.set_with_service(None, 75.0, None).unwrap();
    for d in mgr.displays() {
        assert!((d.brightness_pct - 75.0).abs() < f64::EPSILON);
    }
}

#[test]
fn set_calls_backlight_provider() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![bl_display("amdgpu_bl0", 50, 100)]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.set_with_service(Some(1), 75.0, None).unwrap();
    let calls = bl.set_calls.borrow();
    assert_eq!(calls.get("amdgpu_bl0"), Some(&75u32));
}

#[test]
fn set_invalid_display_returns_error() {
    let ffi_displays = vec![ddc_display("Monitor", 50, 100)];
    let bl = MockBacklightProvider::new(vec![]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    let result = mgr.set_with_service(Some(5), 50.0, None);
    assert!(result.is_err());
}

#[test]
fn set_invalid_display_zero_returns_error() {
    let ffi_displays = vec![ddc_display("Monitor", 50, 100)];
    let bl = MockBacklightProvider::new(vec![]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    let result = mgr.set_with_service(Some(0), 50.0, None);
    assert!(result.is_err());
}

#[test]
fn increase_all_backlight_displays() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![
        bl_display("bl0", 50, 100),
        bl_display("bl1", 30, 100),
    ]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.increase_with_service(None, 10.0, None).unwrap();
    assert!((mgr.displays()[0].brightness_pct - 60.0).abs() < f64::EPSILON);
    assert!((mgr.displays()[1].brightness_pct - 40.0).abs() < f64::EPSILON);
}

#[test]
fn decrease_all_backlight_displays() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![
        bl_display("bl0", 50, 100),
        bl_display("bl1", 30, 100),
    ]);
    let settings = Settings::default();
    let mut mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();

    mgr.decrease_with_service(None, 10.0, None).unwrap();
    assert!((mgr.displays()[0].brightness_pct - 40.0).abs() < f64::EPSILON);
    assert!((mgr.displays()[1].brightness_pct - 20.0).abs() < f64::EPSILON);
}

#[test]
fn detect_with_custom_name_from_settings() {
    let ffi_displays = vec![ddc_display("Generic", 50, 100)];
    let bl = MockBacklightProvider::new(vec![]);
    let mut settings = Settings::default();
    settings.set_custom_name("ddc:1", "My Custom Name");

    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();
    assert_eq!(mgr.displays()[0].name, "My Custom Name");
}

#[test]
fn detect_with_zero_max_brightness() {
    let ffi_displays = vec![ddc_display("Monitor", 50, 0)];
    let bl = MockBacklightProvider::new(vec![]);
    let settings = Settings::default();

    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();
    assert!((mgr.displays()[0].brightness_pct - 0.0).abs() < f64::EPSILON);
}

#[test]
fn detect_multiple_backlights_get_unique_names() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![
        bl_display("amdgpu_bl0", 50, 100),
        bl_display("intel_backlight", 30, 100),
    ]);
    let settings = Settings::default();

    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();
    assert_eq!(mgr.count(), 2);
    assert_eq!(mgr.displays()[0].name, "Backlight (amdgpu_bl0)");
    assert_eq!(mgr.displays()[1].name, "Backlight (intel_backlight)");
}

#[test]
fn detect_single_backlight_gets_builtin_name() {
    let ffi_displays: Vec<DdcDisplayInfo> = vec![];
    let bl = MockBacklightProvider::new(vec![bl_display("amdgpu_bl0", 50, 100)]);
    let settings = Settings::default();

    let mgr = DisplayManager::detect_with_ffi(&ffi_displays, &bl, &settings).unwrap();
    assert_eq!(mgr.displays()[0].name, "Built-in Display");
}
