use std::{
    cell::{Cell, RefCell},
    path::Path,
    rc::Rc,
};

use rstest::rstest;
use tpm2_device::{with_device, TpmDevice, TpmDeviceError};

#[rstest]
#[case(TpmDeviceError::AlreadyBorrowed, "device is already borrowed")]
#[case(TpmDeviceError::Interrupted, "operation interrupted by user")]
#[case(TpmDeviceError::InvalidResponse, "invalid response")]
#[case(TpmDeviceError::NotAvailable, "device not available")]
#[case(TpmDeviceError::Timeout, "TPM command timed out")]
#[case(TpmDeviceError::UnexpectedEof, "unexpected EOF")]
fn tpm_device_error_display(#[case] error: TpmDeviceError, #[case] expected: &str) {
    assert_eq!(error.to_string(), expected);
}

#[derive(Debug)]
enum WithDeviceError {
    Device(TpmDeviceError),
}

impl From<TpmDeviceError> for WithDeviceError {
    fn from(err: TpmDeviceError) -> Self {
        WithDeviceError::Device(err)
    }
}

#[derive(Clone, Copy)]
enum WithDeviceCase {
    NoDevice,
    AlreadyBorrowed,
}

#[rstest]
#[case(WithDeviceCase::NoDevice)]
#[case(WithDeviceCase::AlreadyBorrowed)]
fn with_device_errors(#[case] scenario: WithDeviceCase) {
    match scenario {
        WithDeviceCase::NoDevice => {
            let result: Result<(), WithDeviceError> =
                with_device::<_, (), WithDeviceError>(None, |_device| Ok(()));
            assert!(matches!(
                result,
                Err(WithDeviceError::Device(TpmDeviceError::NotAvailable))
            ));
        }
        WithDeviceCase::AlreadyBorrowed => {
            let device = TpmDevice::builder()
                .with_path(Path::new("/dev/null"))
                .build()
                .expect("failed to open /dev/null for TpmDevice");
            let device = Rc::new(RefCell::new(device));
            let _guard = device.borrow_mut();

            let result: Result<(), WithDeviceError> =
                with_device::<_, (), WithDeviceError>(Some(device.clone()), |_device| Ok(()));
            assert!(matches!(
                result,
                Err(WithDeviceError::Device(TpmDeviceError::AlreadyBorrowed))
            ));
        }
    }
}

#[test]
fn with_device_success() {
    let device = TpmDevice::builder()
        .with_path(Path::new("/dev/null"))
        .build()
        .expect("failed to open /dev/null for TpmDevice");
    let device = Rc::new(RefCell::new(device));
    let called = Cell::new(false);

    let result: Result<u32, WithDeviceError> = with_device(Some(device.clone()), |_dev| {
        called.set(true);
        Ok(7)
    });

    assert!(called.get());
    assert!(matches!(result, Ok(7)));
}
