/*
 * CoolerControl - monitor and control your cooling and other devices
 * Copyright (c) 2021-2025  Guy Boldon, Eren Simsek and contributors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use crate::cc_fs;
use crate::device::ChannelStatus;
use crate::repositories::hwmon::hwmon_repo::{
    AutoCurveInfo, HwmonChannelCapabilities, HwmonChannelInfo, HwmonChannelType, HwmonDriverInfo,
};
use crate::repositories::hwmon::{auto_curve, devices};
use anyhow::{anyhow, Context, Result};
use futures_util::future::{join3, join_all};
use log::{debug, error, info, log_enabled, trace, warn};
use regex::Regex;
use std::collections::HashMap;
use std::io::{Error, ErrorKind};
use std::ops::Not;
use std::path::{Path, PathBuf};

const PATTERN_PWM_FILE_NUMBER: &str = r"^pwm(?P<number>\d+)$";
const PATTERN_FAN_INPUT_FILE_NUMBER: &str = r"^fan(?P<number>\d+)_input$";
pub const PWM_ENABLE_MANUAL_VALUE: u8 = 1;
pub const PWM_ENABLE_AUTO_VALUE: u8 = 2;
pub const PWM_ENABLE_NCT6775_SMART_FAN_IV_VALUE: u8 = 5;
macro_rules! format_fan_input { ($($arg:tt)*) => {{ format!("fan{}_input", $($arg)*) }}; }
macro_rules! format_fan_label { ($($arg:tt)*) => {{ format!("fan{}_label", $($arg)*) }}; }
macro_rules! format_pwm { ($($arg:tt)*) => {{ format!("pwm{}", $($arg)*) }}; }
macro_rules! format_pwm_mode { ($($arg:tt)*) => {{ format!("pwm{}_mode", $($arg)*) }}; }
macro_rules! format_pwm_enable { ($($arg:tt)*) => {{ format!("pwm{}_enable", $($arg)*) }}; }

/// Initialize all applicable fans
pub async fn init_fans(base_path: &Path, device_name: &str) -> Result<Vec<HwmonChannelInfo>> {
    let dir_entries = cc_fs::read_dir(base_path)?;
    let mut fan_caps = HashMap::new();
    for entry in dir_entries {
        let os_file_name = entry?.file_name();
        let file_name = os_file_name.to_str().context("File Name should be a str")?;
        detect_pwm(base_path, file_name, &mut fan_caps).await?;
        detect_rpm(base_path, file_name, &mut fan_caps).await?;
    }
    let mut fans = caps_to_hwmon_fans(base_path, device_name, fan_caps).await?;
    fans.sort_by(|c1, c2| c1.number.cmp(&c2.number));
    auto_curve::init_auto_curve_fans(base_path, &mut fans, device_name).await?;
    trace!(
        "Hwmon pwm fans detected: {fans:?} for {}",
        base_path.display()
    );
    Ok(fans)
}

/// Detects if a fan has pwm capability and pwm-write capabilities.
async fn detect_pwm(
    base_path: &Path,
    file_name: &str,
    fan_caps: &mut HashMap<u8, HwmonChannelCapabilities>,
) -> Result<()> {
    let regex_pwm_file = Regex::new(PATTERN_PWM_FILE_NUMBER)?;
    if regex_pwm_file.is_match(file_name).not() {
        return Ok(()); // skip if not a pwm file
    }
    let channel_number: u8 = regex_pwm_file
        .captures(file_name)
        .context("PWM Number should exist")?
        .name("number")
        .context("Number Group should exist")?
        .as_str()
        .parse()?;
    if get_pwm_duty(base_path, &channel_number, None, true)
        .await
        .is_none()
    {
        return Ok(()); // skip if pwm file isn't readable
    }
    let pwm_writable = determine_pwm_writable(base_path, channel_number);
    let caps = fan_caps
        .entry(channel_number)
        .or_insert(HwmonChannelCapabilities::empty());
    caps.insert(HwmonChannelCapabilities::PWM);
    caps.set(HwmonChannelCapabilities::FAN_WRITABLE, pwm_writable);
    Ok(())
}

/// Detects if a fan has rpm display capability.
pub async fn detect_rpm(
    base_path: &Path,
    file_name: &str,
    fan_caps: &mut HashMap<u8, HwmonChannelCapabilities>,
) -> Result<()> {
    let regex_fan_input_file = Regex::new(PATTERN_FAN_INPUT_FILE_NUMBER)?;
    if regex_fan_input_file.is_match(file_name).not() {
        return Ok(()); // skip if not a pwm file
    }
    let channel_number: u8 = regex_fan_input_file
        .captures(file_name)
        .context("Fan Number should exist")?
        .name("number")
        .context("Number Group should exist")?
        .as_str()
        .parse()?;
    if get_fan_rpm(base_path, &channel_number, None, true)
        .await
        .is_none()
    {
        return Ok(()); // skip if rpm file isn't readable
    }
    fan_caps
        .entry(channel_number)
        .or_insert(HwmonChannelCapabilities::empty())
        .insert(HwmonChannelCapabilities::RPM);
    Ok(())
}

/// Converts fan capabilities to `HwmonChannelInfo`
async fn caps_to_hwmon_fans(
    base_path: &Path,
    device_name: &str,
    fan_caps: HashMap<u8, HwmonChannelCapabilities>,
) -> Result<Vec<HwmonChannelInfo>> {
    let mut fans = vec![];
    for (channel_number, fan_cap) in fan_caps {
        let current_pwm_enable = get_current_pwm_enable(base_path, &channel_number).await;
        let pwm_enable_default = adjusted_pwm_default(current_pwm_enable, device_name);
        let channel_name = get_fan_channel_name(channel_number);
        let label = get_fan_channel_label(base_path, &channel_number).await;
        // deprecated setting:
        // determine_pwm_mode_support(base_path, &channel_number).await;
        if fan_cap.is_non_controllable_rpm_fan() {
            info!(
                "Uncontrollable RPM-only fan found at {}/fan{channel_number}_input",
                base_path.display()
            );
        }
        let pwm_path = if fan_cap.has_pwm() {
            Some(base_path.join(format_pwm!(channel_number)))
        } else {
            None
        };
        let rpm_path = if fan_cap.has_rpm() {
            Some(base_path.join(format_fan_input!(channel_number)))
        } else {
            None
        };
        fans.push(HwmonChannelInfo {
            hwmon_type: HwmonChannelType::Fan,
            number: channel_number,
            pwm_enable_default,
            name: channel_name,
            label,
            caps: fan_cap,
            auto_curve: AutoCurveInfo::None,
            pwm_path,
            rpm_path,
            temp_path: None,
        });
    }
    Ok(fans)
}

/// Streams fan statuses to `sink` one channel at a time as each read
/// completes, returning whether any expected field read failed.
/// Failed reads for any expected field (pwm when `has_pwm`, rpm when
/// `has_rpm`) omit the whole channel entry rather than pushing a
/// partial status with `None`. Pushing a partial or all-`None` status
/// would block `FailsafeStatusData::overwrite_missing` from
/// substituting failsafe values, because the channel name would still
/// appear in the fresh-names set. Below the threshold the cache keeps
/// the last-known-good reading; above the threshold the overlay
/// installs the failsafe values (rpm 0, duty 0).
/// Callers that want a buffered `Vec` should use `extract_fan_statuses`.
pub async fn stream_fan_statuses<F>(driver: &HwmonDriverInfo, mut sink: F) -> bool
where
    F: FnMut(ChannelStatus),
{
    let mut any_failure = false;
    for channel in &driver.channels {
        if channel.hwmon_type != HwmonChannelType::Fan {
            continue;
        }
        match read_one_fan_status(driver, channel).await {
            Some(status) => sink(status),
            None => any_failure = true,
        }
    }
    any_failure
}

/// Reads pwm-duty and fan-rpm for one channel and returns the
/// resulting `ChannelStatus`, or `None` if all expected fields
/// failed. Pulled out so callers that need per-channel permit
/// handoff (e.g. the preload loop on a slow device) can acquire
/// the device permit just for the duration of one read instead
/// of holding it across the whole device's channel set.
pub async fn read_one_fan_status(
    driver: &HwmonDriverInfo,
    channel: &HwmonChannelInfo,
) -> Option<ChannelStatus> {
    debug_assert_eq!(channel.hwmon_type, HwmonChannelType::Fan);
    let fan_duty = if channel.caps.has_pwm() {
        get_pwm_duty(
            &driver.path,
            &channel.number,
            channel.pwm_path.as_ref(),
            log_enabled!(log::Level::Debug),
        )
        .await
    } else {
        None
    };
    let fan_rpm = if channel.caps.has_rpm() {
        get_fan_rpm(
            &driver.path,
            &channel.number,
            channel.rpm_path.as_ref(),
            log_enabled!(log::Level::Debug),
        )
        .await
    } else {
        None
    };
    let expected_pwm_failed = channel.caps.has_pwm() && fan_duty.is_none();
    let expected_rpm_failed = channel.caps.has_rpm() && fan_rpm.is_none();
    if expected_pwm_failed || expected_rpm_failed {
        return None;
    }
    Some(ChannelStatus {
        name: channel.name.clone(),
        rpm: fan_rpm,
        duty: fan_duty,
        ..Default::default()
    })
}

/// Reads only the RPM portion of a fan channel. Used by the
/// slow-device preload path: when the duty cache is fresh, we can
/// skip the slow PWM duty read and just refresh RPM (which is
/// device-controlled feedback and cannot be cached). Returns
/// `None` if RPM was expected but the read failed; absent RPM cap
/// returns `Some(None)` so the caller can synthesize a status
/// from the cached duty alone.
pub async fn read_one_fan_rpm_only(
    driver: &HwmonDriverInfo,
    channel: &HwmonChannelInfo,
) -> Option<Option<u32>> {
    debug_assert_eq!(channel.hwmon_type, HwmonChannelType::Fan);
    if channel.caps.has_rpm().not() {
        return Some(None);
    }
    let fan_rpm = get_fan_rpm(
        &driver.path,
        &channel.number,
        channel.rpm_path.as_ref(),
        log_enabled!(log::Level::Debug),
    )
    .await?;
    Some(Some(fan_rpm))
}

/// Buffered wrapper over `stream_fan_statuses` for callers that want
/// an owned `Vec<ChannelStatus>` (for example, the reinit path).
pub async fn extract_fan_statuses(driver: &HwmonDriverInfo) -> (Vec<ChannelStatus>, bool) {
    let fan_channel_count = driver
        .channels
        .iter()
        .filter(|c| c.hwmon_type == HwmonChannelType::Fan)
        .count();
    let mut fans = Vec::with_capacity(fan_channel_count);
    let any_failure = stream_fan_statuses(driver, |status| fans.push(status)).await;
    (fans, any_failure)
}

#[allow(dead_code)]
/// This is the concurrent version of the `extract_fan_statuses` function.
pub async fn extract_fan_statuses_concurrently(driver: &HwmonDriverInfo) -> Vec<ChannelStatus> {
    let mut fan_tasks = vec![];
    moro_local::async_scope!(|scope| {
        for channel in &driver.channels {
            if channel.hwmon_type != HwmonChannelType::Fan {
                continue;
            }
            let fan_task = scope.spawn(async {
                moro_local::async_scope!(|channel_scope| {
                    let fan_rpm_task = channel_scope.spawn(async {
                        if channel.caps.has_rpm() {
                            get_fan_rpm(
                                &driver.path,
                                &channel.number,
                                channel.rpm_path.as_ref(),
                                false,
                            )
                            .await
                        } else {
                            None
                        }
                    });
                    let fan_duty_task = channel_scope.spawn(async {
                        if channel.caps.has_pwm() {
                            get_pwm_duty(
                                &driver.path,
                                &channel.number,
                                channel.pwm_path.as_ref(),
                                false,
                            )
                            .await
                        } else {
                            None
                        }
                    });
                    let fan_pwm_mode_task = channel_scope.spawn(async {
                        if channel.caps.has_pwm_mode() {
                            cc_fs::read_sysfs(driver.path.join(format_pwm_mode!(channel.number)))
                                .await
                                .and_then(check_parsing_8)
                                .ok()
                        } else {
                            None
                        }
                    });
                    let (fan_rpm, fan_duty, fan_pwm_mode) =
                        join3(fan_rpm_task, fan_duty_task, fan_pwm_mode_task).await;
                    ChannelStatus {
                        name: channel.name.clone(),
                        rpm: fan_rpm,
                        duty: fan_duty,
                        pwm_mode: fan_pwm_mode,
                        ..Default::default()
                    }
                })
                .await
            });
            fan_tasks.push(fan_task);
        }
        join_all(fan_tasks).await
    })
    .await
}

async fn get_pwm_duty(
    base_path: &Path,
    channel_number: &u8,
    pwm_path: Option<&PathBuf>,
    log_error: bool,
) -> Option<f64> {
    let pwm_path = match pwm_path {
        Some(path) => path,
        None => &base_path.join(format_pwm!(channel_number)),
    };
    match cc_fs::read_sysfs(pwm_path)
        .await
        .and_then(check_parsing_8)
        .map(pwm_value_to_duty)
    {
        Ok(duty) => {
            debug!("hwmon read {}: {duty}% duty", pwm_path.display());
            Some(duty)
        }
        Err(err) => {
            // Known drivers that refuse pwmX reads in auto mode:
            //   - gpd_fan:  EOPNOTSUPP (io::ErrorKind::Unsupported)
            //   - dell_smm: ENODATA    (raw os error 61)
            let is_kernel_refusal = err.downcast_ref::<Error>().is_some_and(|io_err| {
                io_err.raw_os_error().is_some() && io_err.kind() != ErrorKind::NotFound
            });
            if is_kernel_refusal {
                if let Some(pwm_enable) = get_current_pwm_enable(base_path, channel_number).await {
                    if pwm_enable >= PWM_ENABLE_AUTO_VALUE {
                        debug!(
                            "pwmX read refused by kernel driver in auto mode \
                             (pwm_enable={pwm_enable}) at {}; returning 100% duty",
                            pwm_path.display()
                        );
                        return Some(100.0);
                    }
                }
            }
            if log_error {
                warn!(
                    "Could not read fan pwm value at {} ; {err}",
                    pwm_path.display()
                );
            }
            None
        }
    }
}

pub async fn get_fan_rpm(
    base_path: &Path,
    channel_number: &u8,
    rpm_path: Option<&PathBuf>,
    log_error: bool,
) -> Option<u32> {
    let fan_input_path = match rpm_path {
        Some(path) => path,
        None => &base_path.join(format_fan_input!(channel_number)),
    };
    cc_fs::read_sysfs(fan_input_path)
        .await
        .and_then(check_parsing_32)
        // Edge case where on spin-up the output is max value until it begins moving
        .map(|rpm| if rpm >= u32::from(u16::MAX) { 0 } else { rpm })
        .inspect(|rpm| debug!("hwmon read {}: {rpm} RPM", fan_input_path.display()))
        .inspect_err(|err| {
            if log_error {
                warn!(
                    "Could not read fan rpm value at {}: {err}",
                    fan_input_path.display()
                );
            }
        })
        .ok()
}

/// Not all drivers have `pwm_enable` for their fans. In that case there is no "automatic" mode available.
///  Example `pwm_enable` setting options: (1 and 2 are the most common)
///  - 0 : full speed / off (not used/recommended)
///  - 1 : manual control (setting pwm* will adjust fan speed)
///  - 2 : automatic (primarily used by on-board/chip fan control, like laptops or mobos without smart fan control)
///  - 3 : "Fan Speed Cruise" mode (?)
///  - 4 : "Smart Fan III" mode (NCT6775F only)
///  - 5 : "Smart Fan IV" mode (modern `MoBo`'s with build-in smart fan control probably use this)
async fn get_current_pwm_enable(base_path: &Path, channel_number: &u8) -> Option<u8> {
    let pwm_enable_path = base_path.join(format_pwm_enable!(channel_number));
    let current_pwm_enable = cc_fs::read_sysfs(&pwm_enable_path)
        .await
        .and_then(check_parsing_8)
        .ok();
    if current_pwm_enable.is_none() {
        debug!(
            "No pwm_enable found for fan#{channel_number} at location:{}",
            pwm_enable_path.display()
        );
    }
    current_pwm_enable
}

#[allow(clippy::needless_pass_by_value)]
pub fn check_parsing_8(content: String) -> Result<u8> {
    match content.trim().parse::<u8>() {
        Ok(value) => Ok(value),
        Err(err) => Err(Error::new(ErrorKind::InvalidData, err.to_string()).into()),
    }
}

#[allow(clippy::needless_pass_by_value)]
pub fn check_parsing_32(content: String) -> Result<u32> {
    match content.trim().parse::<u32>() {
        Ok(value) => Ok(value),
        Err(err) => Err(Error::new(ErrorKind::InvalidData, err.to_string()).into()),
    }
}

/// If a `HWMon` driver has not set the writable bit on the sysfs file, then that
/// indicates that the pwm value is read-only and not configurable.
fn determine_pwm_writable(base_path: &Path, channel_number: u8) -> bool {
    let pwm_path = base_path.join(format_pwm!(channel_number));
    let pwm_writable = cc_fs::metadata(&pwm_path)
        .inspect_err(|_| error!("PWM file metadata is not readable: {}", pwm_path.display()))
        // This check should be sufficient, as we're running as root:
        .is_ok_and(|att| att.permissions().readonly().not());
    if pwm_writable.not() {
        info!(
            "PWM fan at {} is NOT writable - \
            Fan control is not currently supported by the installed driver.",
            pwm_path.display()
        );
    }
    pwm_writable
}

/// We save the existing `pwm_enable` setting and applying the Default Profile/shutting down the
/// service will then revert to that setting - which is usually 'auto' set by the bios on boot -
/// but not necessarily and not all devices support an auto setting.
///
/// This means we can not safely apply 'auto' to `pwm_enable` indiscriminately and therefor we use
/// whatever the initial setting was as the Default.
///
/// Note: Some drivers should have an automatic fallback for safety reasons,
/// regardless of the current value.
fn adjusted_pwm_default(current_pwm_enable: Option<u8>, device_name: &str) -> Option<u8> {
    current_pwm_enable.map(|original_value| {
        if devices::device_needs_pwm_fallback(device_name) {
            2
        } else {
            original_value
        }
    })
}

/// Reads the contents of the fan?_label file specified by `base_path` and
/// `channel_number`, trims any leading or trailing whitespace, and returns the resulting string if it
/// is not empty.
///
/// Arguments:
///
/// * `base_path`: A `PathBuf` object representing the base path where the file `fan{}_label` is
///   located.
/// * `channel_number`: The `channel_number` parameter is an unsigned 8-bit integer that represents
///   the channel number. It is used to construct the file path for reading the label.
///
/// Returns:
///
/// an `Option<String>`.
pub async fn get_fan_channel_label(base_path: &Path, channel_number: &u8) -> Option<String> {
    cc_fs::read_txt(base_path.join(format_fan_label!(channel_number)))
        .await
        .ok()
        .and_then(|label| {
            let fan_label = label.trim();
            if fan_label.is_empty() {
                warn!(
                    "Fan label is empty for {}/fan{channel_number}_label",
                    base_path.display()
                );
                None
            } else {
                Some(fan_label.to_string())
            }
        })
}

/// Returns a string that represents a unique channel name/ID.
///
/// Arguments:
///
/// * `channel_number`: The `channel_number` parameter is a reference to an unsigned 8-bit integer
///   (`&u8`).
///
/// Returns:
///
/// * A `String` that represents a unique channel name/ID.
pub fn get_fan_channel_name(channel_number: u8) -> String {
    format!("fan{channel_number}")
}

/// This sets `pwm_enable` to the default value,
/// unless it's currently set to "auto-mode" (>1), then it will be left on auto mode.
/// This is mostly used when shutting down the service to revert to the default value,
/// but not necessarily and not all devices support an auto setting.
pub async fn set_pwm_enable_to_default_or_auto(
    base_path: &Path,
    channel_info: &HwmonChannelInfo,
) -> Result<()> {
    let Some(default_value) = channel_info.pwm_enable_default else {
        // not all devices have pwm_enable available
        return Ok(());
    };
    let path_pwm_enable = base_path.join(format_pwm_enable!(channel_info.number));
    let current_pwm_enable = cc_fs::read_sysfs(&path_pwm_enable)
        .await
        .and_then(check_parsing_8)?;
    if current_pwm_enable < PWM_ENABLE_AUTO_VALUE && current_pwm_enable != default_value {
        if let Err(err) = write_pwm_enable(&path_pwm_enable, default_value).await {
            warn!("Failed to reset pwm_enable to default: {err}");
        }
    }
    debug!(
        "Reset Hwmon value at {}/pwm{}_enable to default/auto value",
        base_path.display(),
        channel_info.number
    );
    Ok(())
}

/// This sets `pwm_enable` to the desired value. Unlike other operations,
/// it will not check if it's already set to the desired value.
/// See also `get_current_pwm_enable`.
pub async fn set_pwm_enable(
    pwm_enable_value: u8,
    base_path: &Path,
    channel_info: &HwmonChannelInfo,
) -> Result<()> {
    if channel_info.pwm_enable_default.is_none() {
        // not all devices have pwm_enable available
        return Ok(());
    }
    if pwm_enable_value > 5 {
        return Err(anyhow!(
            "pwm_enable value must be between 0 and 5 (inclusive)"
        ));
    }
    let path_pwm_enable = base_path.join(format_pwm_enable!(channel_info.number));
    write_pwm_enable(&path_pwm_enable, pwm_enable_value).await
}

/// This sets `pwm_enable` to the desired value if it's not already set to the desired value.
/// See also `get_current_pwm_enable`.
pub async fn set_pwm_enable_if_not_already(
    pwm_enable_value: u8,
    base_path: &Path,
    channel_info: &HwmonChannelInfo,
) -> Result<()> {
    if channel_info.pwm_enable_default.is_none() {
        // not all devices have pwm_enable available
        return Ok(());
    }
    let path_pwm_enable = base_path.join(format_pwm_enable!(channel_info.number));
    let current_pwm_enable = cc_fs::read_sysfs(&path_pwm_enable)
        .await
        .and_then(check_parsing_8)?;
    if current_pwm_enable == pwm_enable_value {
        Ok(())
    } else {
        write_pwm_enable(&path_pwm_enable, pwm_enable_value).await
    }
}

async fn write_pwm_enable(path_pwm_enable: &Path, pwm_enable_value: u8) -> Result<()> {
    cc_fs::write_string(&path_pwm_enable, pwm_enable_value.to_string())
        .await
        .inspect(|()| {
            debug!(
                "Applied pwm_enable for {} of {pwm_enable_value}",
                path_pwm_enable.display()
            );
        })
        .map_err(|err| {
            anyhow!(
                "Unable to set pwm_enable for {} to {pwm_enable_value}. \
                    Most likely because of a limitation set by the driver or a BIOS setting; \
                    Error: {err}",
                path_pwm_enable.display()
            )
        })
}

pub async fn set_pwm_duty(
    base_path: &Path,
    channel_info: &HwmonChannelInfo,
    speed_duty: u8,
) -> Result<()> {
    let pwm_value = duty_to_pwm_value(speed_duty);
    let pwm_path = match channel_info.pwm_path.as_ref() {
        Some(path) => path,
        None => &base_path.join(format_pwm!(channel_info.number)),
    };
    cc_fs::write_string(&pwm_path, pwm_value.to_string())
        .await
        .map_err(|err| {
            anyhow!(
                "Unable to set PWM value {pwm_value} for {} Reason: {err}",
                pwm_path.display()
            )
        })
}

/// Converts a pwm value (0-255) to a duty value (0-100%)
pub fn pwm_value_to_duty(pwm_value: u8) -> f64 {
    ((f64::from(pwm_value) / 0.255).round() / 10.0).round()
}

/// Converts a duty value (0-100%) to a pwm value (0-255)
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn duty_to_pwm_value(speed_duty: u8) -> u8 {
    let clamped_duty = f64::from(speed_duty.clamp(0, 100));
    // round only takes the first decimal digit into consideration, so we adjust to have it take the first two digits into consideration.
    ((clamped_duty * 25.5).round() / 10.0).round() as u8
}

/// Tests
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
    use super::*;
    use serial_test::serial;
    use std::path::{Path, PathBuf};
    use uuid::Uuid;

    const TEST_BASE_PATH_STR: &str = "/tmp/coolercontrol-tests-";

    struct HwmonFileContext {
        test_base_path: PathBuf,
    }

    async fn setup() -> HwmonFileContext {
        let test_base_path =
            Path::new(&(TEST_BASE_PATH_STR.to_string() + &Uuid::new_v4().to_string()))
                .to_path_buf();
        cc_fs::create_dir_all(&test_base_path).await.unwrap();
        HwmonFileContext { test_base_path }
    }

    async fn teardown(ctx: &HwmonFileContext) {
        cc_fs::remove_dir_all(&ctx.test_base_path).await.unwrap();
    }

    #[test]
    #[serial]
    fn find_fan_dir_not_exist() {
        cc_fs::test_runtime(async {
            // given:
            let test_base_path = Path::new("/tmp/does_not_exist").to_path_buf();
            let device_name = "Test Driver".to_string();

            // when:
            let fans_result = init_fans(&test_base_path, &device_name).await;

            // then:
            assert!(fans_result.is_err());
            assert!(fans_result
                .map_err(|err| err.to_string().contains("No such file or directory"))
                .unwrap_err());
        });
    }

    #[test]
    #[serial]
    fn find_fan() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(
                test_base_path.join("pwm1"),
                b"127".to_vec(), // duty
            )
            .await
            .unwrap();
            cc_fs::write(
                test_base_path.join("fan1_input"),
                b"3000".to_vec(), // rpm
            )
            .await
            .unwrap();
            let device_name = "Test Driver".to_string();

            // when:
            let fans_result = init_fans(test_base_path, &device_name).await;

            // then:
            // println!("RESULT: {:?}", fans_result);
            teardown(&ctx).await;
            assert!(fans_result.is_ok());
            let fans = fans_result.unwrap();
            assert_eq!(fans.len(), 1);
            assert_eq!(fans[0].hwmon_type, HwmonChannelType::Fan);
            assert_eq!(fans[0].name, "fan1");
            assert!(fans[0].caps.has_pwm_mode().not());
            assert_eq!(fans[0].pwm_enable_default, None);
            assert_eq!(fans[0].number, 1);
            assert!(fans[0].caps.is_fan_controllable());
        });
    }

    #[test]
    #[serial]
    fn find_fan_pwm_only() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(
                test_base_path.join("pwm1"),
                b"127".to_vec(), // duty
            )
            .await
            .unwrap();
            let device_name = "Test Driver".to_string();

            // when:
            let fans_result = init_fans(test_base_path, &device_name).await;

            // then:
            teardown(&ctx).await;
            assert!(fans_result.is_ok());
            let fans = fans_result.unwrap();
            assert_eq!(fans.len(), 1);
            assert_eq!(fans[0].hwmon_type, HwmonChannelType::Fan);
            assert_eq!(fans[0].name, "fan1");
            assert!(fans[0].caps.has_pwm_mode().not());
            assert_eq!(fans[0].pwm_enable_default, None);
            assert_eq!(fans[0].number, 1);
            assert!(fans[0].caps.is_fan_controllable());
        });
    }

    #[test]
    #[serial]
    fn find_fan_rpm_only() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(
                test_base_path.join("fan1_input"),
                b"3000".to_vec(), // rpm
            )
            .await
            .unwrap();
            let device_name = "Test Driver".to_string();

            // when:
            let fans_result = init_fans(test_base_path, &device_name).await;

            // then:
            // println!("RESULT: {:?}", fans_result);
            teardown(&ctx).await;
            assert!(fans_result.is_ok());
            let fans = fans_result.unwrap();
            assert_eq!(fans.len(), 1);
            assert_eq!(fans[0].hwmon_type, HwmonChannelType::Fan);
            assert_eq!(fans[0].name, "fan1");
            assert!(fans[0].caps.has_pwm_mode().not());
            assert_eq!(fans[0].pwm_enable_default, None);
            assert_eq!(fans[0].number, 1);
            assert!(fans[0].caps.is_fan_controllable().not());
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_enable_to_default() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(test_base_path.join("pwm1_enable"), b"1".to_vec())
                .await
                .unwrap();
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: Some(2),
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result = set_pwm_enable_to_default_or_auto(test_base_path, &channel_info).await;

            // then:
            let current_pwm_enable = cc_fs::read_sysfs(&test_base_path.join("pwm1_enable"))
                .await
                .unwrap();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert_eq!(current_pwm_enable, "2");
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_enable_to_default_doesnt_exist() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: None,
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result = set_pwm_enable_to_default_or_auto(test_base_path, &channel_info).await;

            // then:
            let pwm_enable_doesnt_exist = cc_fs::read_sysfs(&test_base_path.join("pwm1_enable"))
                .await
                .is_err();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert!(pwm_enable_doesnt_exist);
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_enable_to_default_auto() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            // current value is already auto (2)
            cc_fs::write(test_base_path.join("pwm1_enable"), b"2".to_vec())
                .await
                .unwrap();
            // default is manual (1) but should not override auto
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: Some(1),
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result = set_pwm_enable_to_default_or_auto(test_base_path, &channel_info).await;

            // then:
            let current_pwm_enable = cc_fs::read_sysfs(&test_base_path.join("pwm1_enable"))
                .await
                .unwrap();
            teardown(&ctx).await;
            assert!(result.is_ok());
            // remains on auto (2)
            assert_eq!(current_pwm_enable, "2");
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_enable() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(test_base_path.join("pwm1_enable"), b"2".to_vec())
                .await
                .unwrap();
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: Some(2),
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result =
                set_pwm_enable(PWM_ENABLE_MANUAL_VALUE, test_base_path, &channel_info).await;

            // then:
            let current_pwm_enable = cc_fs::read_sysfs(&test_base_path.join("pwm1_enable"))
                .await
                .unwrap();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert_eq!(current_pwm_enable, "1");
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_enable_doesnt_exist() {
        // test to make sure we don't return an Err if pwm_enable doesn't exist
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: None,
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result =
                set_pwm_enable(PWM_ENABLE_MANUAL_VALUE, test_base_path, &channel_info).await;

            // then:
            let pwm_enable_doesnt_exist = cc_fs::read_sysfs(&test_base_path.join("pwm1_enable"))
                .await
                .is_err();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert!(pwm_enable_doesnt_exist);
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_enable_if_not_already_set() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(test_base_path.join("pwm1_enable"), b"0".to_vec())
                .await
                .unwrap();
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: Some(2),
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result = set_pwm_enable_if_not_already(
                PWM_ENABLE_MANUAL_VALUE,
                test_base_path,
                &channel_info,
            )
            .await;

            // then:
            let current_pwm_enable = cc_fs::read_sysfs(&test_base_path.join("pwm1_enable"))
                .await
                .unwrap();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert_eq!(current_pwm_enable, "1");
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_duty() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(test_base_path.join("pwm1"), b"255".to_vec())
                .await
                .unwrap();
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: Some(2),
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result = set_pwm_duty(test_base_path, &channel_info, 50).await;

            // then:
            let current_duty = cc_fs::read_sysfs(&test_base_path.join("pwm1"))
                .await
                .and_then(check_parsing_8)
                .map(pwm_value_to_duty)
                .unwrap();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert_eq!(format!("{current_duty:.1}"), "50.0");
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_duty_using_path() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(test_base_path.join("pwm1"), b"255".to_vec())
                .await
                .unwrap();
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: Some(2),
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: Some(test_base_path.join("pwm1")),
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result = set_pwm_duty(test_base_path, &channel_info, 50).await;

            // then:
            let current_duty = cc_fs::read_sysfs(&test_base_path.join("pwm1"))
                .await
                .and_then(check_parsing_8)
                .map(pwm_value_to_duty)
                .unwrap();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert_eq!(format!("{current_duty:.1}"), "50.0");
        });
    }

    #[test]
    #[serial]
    fn test_set_pwm_duty_no_pwm_enable() {
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given:
            let test_base_path = &ctx.test_base_path;
            cc_fs::write(test_base_path.join("pwm1"), b"255".to_vec())
                .await
                .unwrap();
            let channel_info = HwmonChannelInfo {
                hwmon_type: HwmonChannelType::Fan,
                number: 1,
                pwm_enable_default: None,
                name: String::new(),
                label: None,
                caps: HwmonChannelCapabilities::FAN_WRITABLE,
                auto_curve: AutoCurveInfo::None,
                pwm_path: None,
                rpm_path: None,
                temp_path: None,
            };

            // when:
            let result = set_pwm_duty(test_base_path, &channel_info, 50).await;

            // then:
            let current_duty = cc_fs::read_sysfs(&test_base_path.join("pwm1"))
                .await
                .and_then(check_parsing_8)
                .map(pwm_value_to_duty)
                .unwrap();
            teardown(&ctx).await;
            assert!(result.is_ok());
            assert_eq!(current_duty.to_string(), "50");
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_returns_duty_when_file_valid() {
        // Verifies the happy path: a readable pwm file with a valid u8 value is converted to
        // a duty percentage and returned as Some.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: pwm1 = 255 (full speed)
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"255".to_vec())
                .await
                .unwrap();

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, true).await;

            // then:
            teardown(&ctx).await;
            assert_eq!(result, Some(100.0));
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_returns_none_when_file_missing() {
        // Verifies the negative space: when the pwm sysfs file does not exist (ENOENT),
        // the function returns None without panicking.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: no pwm1 file exists

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, false).await;

            // then:
            teardown(&ctx).await;
            assert_eq!(result, None);
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_returns_none_when_file_has_invalid_content() {
        // Verifies that a pwm file containing non-numeric content (parse error) returns None
        // rather than propagating the error upward.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: pwm1 contains text that cannot be parsed as u8
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"not_a_number".to_vec())
                .await
                .unwrap();

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, false).await;

            // then:
            teardown(&ctx).await;
            assert_eq!(result, None);
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_pwm_readable_in_auto_mode() {
        // Verifies the happy path: when pwm is readable in auto mode, the normal duty value
        // is returned without triggering the fallback.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: pwm1 = 128, pwm1_enable = 2 (auto)
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"128".to_vec())
                .await
                .unwrap();
            cc_fs::write(ctx.test_base_path.join("pwm1_enable"), b"2".to_vec())
                .await
                .unwrap();

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, false).await;

            // then: normal read succeeds, no fallback needed
            teardown(&ctx).await;
            assert_eq!(result, Some(pwm_value_to_duty(128)));
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_none_when_file_missing_with_auto_mode() {
        // Verifies that ENOENT (file missing) is excluded from the auto-mode fallback.
        // A missing pwm file means the channel doesn't exist, not a driver refusal.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: no pwm1 file, but pwm1_enable = 2 (auto)
            cc_fs::write(ctx.test_base_path.join("pwm1_enable"), b"2".to_vec())
                .await
                .unwrap();

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, false).await;

            // then: ENOENT is not a kernel refusal — must return None
            teardown(&ctx).await;
            assert_eq!(result, None);
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_none_when_file_missing_no_pwm_enable() {
        // Verifies that ENOENT returns None even when pwm_enable doesn't exist.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: no pwm1 file, no pwm1_enable file

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, false).await;

            // then:
            teardown(&ctx).await;
            assert_eq!(result, None);
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_none_when_parse_error_and_auto_mode() {
        // Verifies that parse errors (InvalidData) are excluded from the auto-mode fallback.
        // A garbled file is not a kernel driver refusal.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: pwm1 has non-numeric content, pwm1_enable = 2 (auto)
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"bad".to_vec())
                .await
                .unwrap();
            cc_fs::write(ctx.test_base_path.join("pwm1_enable"), b"2".to_vec())
                .await
                .unwrap();

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, false).await;

            // then: parse error has no raw_os_error — must return None
            teardown(&ctx).await;
            assert_eq!(result, None);
        });
    }

    #[test]
    #[serial]
    fn get_pwm_duty_none_when_parse_error_and_manual_mode() {
        // Verifies that parse errors return None regardless of pwm_enable value.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: pwm1 has non-numeric content, pwm1_enable = 1 (manual)
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"bad".to_vec())
                .await
                .unwrap();
            cc_fs::write(ctx.test_base_path.join("pwm1_enable"), b"1".to_vec())
                .await
                .unwrap();

            // when:
            let result = get_pwm_duty(&ctx.test_base_path, &1, None, false).await;

            // then: parse error + manual mode = no fallback
            teardown(&ctx).await;
            assert_eq!(result, None);
        });
    }

    // --- extract_fan_statuses: failure indicator ---

    fn make_driver(base_path: &Path, channels: Vec<HwmonChannelInfo>) -> HwmonDriverInfo {
        HwmonDriverInfo {
            name: "test_driver".to_string(),
            path: base_path.to_path_buf(),
            model: None,
            u_id: String::new(),
            channels,
            block_dev_path: None,
            apple_smc: crate::repositories::hwmon::apple_mac_smc::AppleMacSMC::default(),
        }
    }

    fn fan_channel(number: u8, caps: HwmonChannelCapabilities) -> HwmonChannelInfo {
        HwmonChannelInfo {
            hwmon_type: HwmonChannelType::Fan,
            number,
            pwm_enable_default: None,
            name: format!("fan{number}"),
            label: None,
            caps,
            auto_curve: AutoCurveInfo::None,
            pwm_path: None,
            rpm_path: None,
            temp_path: None,
        }
    }

    #[test]
    #[serial]
    fn extract_fan_statuses_no_failure_when_all_reads_succeed() {
        // Verifies that when all fan channels read successfully, the failure
        // indicator is false and values are populated.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: pwm1 and fan1_input exist with valid values.
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"128".to_vec())
                .await
                .unwrap();
            cc_fs::write(ctx.test_base_path.join("fan1_input"), b"1200".to_vec())
                .await
                .unwrap();
            let caps = HwmonChannelCapabilities::PWM | HwmonChannelCapabilities::RPM;
            let driver = make_driver(&ctx.test_base_path, vec![fan_channel(1, caps)]);

            // when:
            let (statuses, any_failure) = extract_fan_statuses(&driver).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure.not());
            assert_eq!(statuses.len(), 1);
            assert!(statuses[0].duty.is_some());
            assert!(statuses[0].rpm.is_some());
        });
    }

    #[test]
    #[serial]
    fn extract_fan_statuses_failure_when_pwm_missing() {
        // Verifies that a channel with PWM capability but no pwm file
        // triggers a failure and is omitted from the returned statuses
        // so the upstream failsafe overlay can substitute safe values.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: fan1_input exists, but no pwm1.
            cc_fs::write(ctx.test_base_path.join("fan1_input"), b"1200".to_vec())
                .await
                .unwrap();
            let caps = HwmonChannelCapabilities::PWM | HwmonChannelCapabilities::RPM;
            let driver = make_driver(&ctx.test_base_path, vec![fan_channel(1, caps)]);

            // when:
            let (statuses, any_failure) = extract_fan_statuses(&driver).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure);
            assert!(statuses.is_empty());
        });
    }

    #[test]
    #[serial]
    fn extract_fan_statuses_failure_when_rpm_missing() {
        // Verifies that a channel with RPM capability but no fan_input
        // file triggers a failure and is omitted from the returned
        // statuses, for the same reason as the PWM-missing case.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: pwm1 exists, but no fan1_input.
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"128".to_vec())
                .await
                .unwrap();
            let caps = HwmonChannelCapabilities::PWM | HwmonChannelCapabilities::RPM;
            let driver = make_driver(&ctx.test_base_path, vec![fan_channel(1, caps)]);

            // when:
            let (statuses, any_failure) = extract_fan_statuses(&driver).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure);
            assert!(statuses.is_empty());
        });
    }

    #[test]
    #[serial]
    fn extract_fan_statuses_failure_when_both_pwm_and_rpm_missing() {
        // Regression: when both expected reads fail, the fan channel
        // must be omitted entirely. Previously an all-`None` entry was
        // pushed, which blocked the failsafe overlay from substituting
        // the configured failsafe values and the channel disappeared
        // from downstream status views.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: neither pwm1 nor fan1_input exist.
            let caps = HwmonChannelCapabilities::PWM | HwmonChannelCapabilities::RPM;
            let driver = make_driver(&ctx.test_base_path, vec![fan_channel(1, caps)]);

            // when:
            let (statuses, any_failure) = extract_fan_statuses(&driver).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure);
            assert!(statuses.is_empty());
        });
    }

    #[test]
    #[serial]
    fn extract_fan_statuses_no_failure_for_rpm_only_channel_without_pwm_cap() {
        // Verifies that a channel without PWM capability does not trigger
        // a failure when pwm returns None (that is expected).
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: fan1_input exists, no pwm1, and channel only has RPM cap.
            cc_fs::write(ctx.test_base_path.join("fan1_input"), b"900".to_vec())
                .await
                .unwrap();
            let caps = HwmonChannelCapabilities::RPM;
            let driver = make_driver(&ctx.test_base_path, vec![fan_channel(1, caps)]);

            // when:
            let (statuses, any_failure) = extract_fan_statuses(&driver).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure.not());
            assert_eq!(statuses.len(), 1);
            assert!(statuses[0].duty.is_none());
            assert!(statuses[0].rpm.is_some());
        });
    }

    // --- stream_fan_statuses: sink contract ---

    #[test]
    #[serial]
    fn stream_fan_statuses_invokes_sink_in_channel_order() {
        // Verifies the streaming variant invokes the sink once per
        // successful channel in channel-number order, matching the
        // buffered version's Vec order.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: three successive fan channels, all readable.
            for number in 1u8..=3 {
                cc_fs::write(
                    ctx.test_base_path.join(format!("pwm{number}")),
                    b"128".to_vec(),
                )
                .await
                .unwrap();
                cc_fs::write(
                    ctx.test_base_path.join(format!("fan{number}_input")),
                    b"1200".to_vec(),
                )
                .await
                .unwrap();
            }
            let caps = HwmonChannelCapabilities::PWM | HwmonChannelCapabilities::RPM;
            let driver = make_driver(
                &ctx.test_base_path,
                vec![
                    fan_channel(1, caps.clone()),
                    fan_channel(2, caps.clone()),
                    fan_channel(3, caps),
                ],
            );

            // when: collect invocations in order.
            let mut received: Vec<String> = Vec::new();
            let any_failure =
                stream_fan_statuses(&driver, |status| received.push(status.name)).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure.not());
            assert_eq!(received, vec!["fan1", "fan2", "fan3"]);
        });
    }

    #[test]
    #[serial]
    fn stream_fan_statuses_skips_sink_on_failure() {
        // Verifies the sink is not invoked for a channel whose
        // expected read fails, and any_failure reflects it.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: fan1 readable, fan2 missing its pwm file.
            cc_fs::write(ctx.test_base_path.join("pwm1"), b"128".to_vec())
                .await
                .unwrap();
            cc_fs::write(ctx.test_base_path.join("fan1_input"), b"1200".to_vec())
                .await
                .unwrap();
            cc_fs::write(ctx.test_base_path.join("fan2_input"), b"900".to_vec())
                .await
                .unwrap();
            // no pwm2 file, but fan2 has PWM cap -> expected-field failure
            let caps = HwmonChannelCapabilities::PWM | HwmonChannelCapabilities::RPM;
            let driver = make_driver(
                &ctx.test_base_path,
                vec![fan_channel(1, caps.clone()), fan_channel(2, caps)],
            );

            // when:
            let mut received: Vec<String> = Vec::new();
            let any_failure =
                stream_fan_statuses(&driver, |status| received.push(status.name)).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure);
            assert_eq!(received, vec!["fan1"]);
        });
    }

    #[test]
    #[serial]
    fn stream_fan_statuses_no_invocation_when_no_channels() {
        // Verifies the sink is never invoked for a driver with no
        // fan channels, and any_failure is false.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            let driver = make_driver(&ctx.test_base_path, vec![]);

            let mut invocations: u32 = 0;
            let any_failure = stream_fan_statuses(&driver, |_| invocations += 1).await;

            teardown(&ctx).await;
            assert_eq!(invocations, 0);
            assert!(any_failure.not());
        });
    }

    #[test]
    #[serial]
    fn extract_fan_statuses_no_failure_when_no_fan_channels() {
        // Verifies that an empty channel list produces no statuses
        // and no failure.
        cc_fs::test_runtime(async {
            let ctx = setup().await;
            // given: driver with no channels.
            let driver = make_driver(&ctx.test_base_path, vec![]);

            // when:
            let (statuses, any_failure) = extract_fan_statuses(&driver).await;

            // then:
            teardown(&ctx).await;
            assert!(any_failure.not());
            assert!(statuses.is_empty());
        });
    }
}
