use async_trait::async_trait;
use log::warn;
use once_cell::sync::OnceCell;
use std::ffi::OsString;
use std::fmt;
use std::io::{self, ErrorKind, Read, Seek, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::SystemTime;

#[cfg(not(target_arch = "wasm32"))]
mod native;
#[cfg(not(target_arch = "wasm32"))]
pub mod remote;
#[cfg(not(target_arch = "wasm32"))]
pub mod sandbox;
#[cfg(target_arch = "wasm32")]
mod wasm;

#[cfg(not(target_arch = "wasm32"))]
pub use native::NativeFsProvider;
#[cfg(not(target_arch = "wasm32"))]
pub use remote::{RemoteFsConfig, RemoteFsProvider};
#[cfg(not(target_arch = "wasm32"))]
pub use sandbox::SandboxFsProvider;
#[cfg(target_arch = "wasm32")]
pub use wasm::PlaceholderFsProvider;

pub mod data_contract;

use data_contract::{
    DataChunkUploadRequest, DataChunkUploadTarget, DataManifestDescriptor, DataManifestRequest,
};

#[async_trait(?Send)]
pub trait FileHandle: Read + Write + Seek + Send + Sync {
    async fn flush_async(&mut self) -> io::Result<()> {
        self.flush()
    }

    async fn sync_all_async(&mut self) -> io::Result<()> {
        self.flush_async().await
    }
}

#[async_trait(?Send)]
impl FileHandle for std::fs::File {
    async fn sync_all_async(&mut self) -> io::Result<()> {
        std::fs::File::sync_all(self)
    }
}

#[derive(Clone, Debug, Default)]
pub struct OpenFlags {
    pub read: bool,
    pub write: bool,
    pub append: bool,
    pub truncate: bool,
    pub create: bool,
    pub create_new: bool,
}

#[derive(Clone, Debug)]
pub struct OpenOptions {
    flags: OpenFlags,
}

impl OpenOptions {
    pub fn new() -> Self {
        Self {
            flags: OpenFlags::default(),
        }
    }

    pub fn read(&mut self, value: bool) -> &mut Self {
        self.flags.read = value;
        self
    }

    pub fn write(&mut self, value: bool) -> &mut Self {
        self.flags.write = value;
        self
    }

    pub fn append(&mut self, value: bool) -> &mut Self {
        self.flags.append = value;
        self
    }

    pub fn truncate(&mut self, value: bool) -> &mut Self {
        self.flags.truncate = value;
        self
    }

    pub fn create(&mut self, value: bool) -> &mut Self {
        self.flags.create = value;
        self
    }

    pub fn create_new(&mut self, value: bool) -> &mut Self {
        self.flags.create_new = value;
        self
    }

    pub fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {
        let resolved = resolve_path(path.as_ref());
        with_provider(|provider| provider.open(&resolved, &self.flags)).map(File::from_handle)
    }

    pub async fn open_async(&self, path: impl AsRef<Path>) -> io::Result<File> {
        let resolved = resolve_path(path.as_ref());
        let provider = current_provider();
        provider
            .open_async(&resolved, &self.flags)
            .await
            .map(File::from_handle)
    }

    pub fn flags(&self) -> &OpenFlags {
        &self.flags
    }
}

impl Default for OpenOptions {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FsFileType {
    Directory,
    File,
    Symlink,
    Other,
    Unknown,
}

#[derive(Clone, Debug)]
pub struct FsMetadata {
    file_type: FsFileType,
    len: u64,
    modified: Option<SystemTime>,
    readonly: bool,
    hash: Option<String>,
}

impl FsMetadata {
    pub fn new(
        file_type: FsFileType,
        len: u64,
        modified: Option<SystemTime>,
        readonly: bool,
    ) -> Self {
        Self {
            file_type,
            len,
            modified,
            readonly,
            hash: None,
        }
    }

    pub fn new_with_hash(
        file_type: FsFileType,
        len: u64,
        modified: Option<SystemTime>,
        readonly: bool,
        hash: Option<String>,
    ) -> Self {
        Self {
            file_type,
            len,
            modified,
            readonly,
            hash,
        }
    }

    pub fn file_type(&self) -> FsFileType {
        self.file_type
    }

    pub fn is_dir(&self) -> bool {
        matches!(self.file_type, FsFileType::Directory)
    }

    pub fn is_file(&self) -> bool {
        matches!(self.file_type, FsFileType::File)
    }

    pub fn is_symlink(&self) -> bool {
        matches!(self.file_type, FsFileType::Symlink)
    }

    pub fn len(&self) -> u64 {
        self.len
    }

    pub fn hash(&self) -> Option<&str> {
        self.hash.as_deref()
    }

    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    pub fn modified(&self) -> Option<SystemTime> {
        self.modified
    }

    pub fn is_readonly(&self) -> bool {
        self.readonly
    }
}

#[derive(Clone, Debug)]
pub struct DirEntry {
    path: PathBuf,
    file_name: OsString,
    file_type: FsFileType,
}

#[derive(Clone, Debug)]
pub struct ReadManyEntry {
    path: PathBuf,
    bytes: Option<Vec<u8>>,
    error: Option<String>,
}

impl ReadManyEntry {
    pub fn new(path: PathBuf, bytes: Option<Vec<u8>>) -> Self {
        Self {
            path,
            bytes,
            error: None,
        }
    }

    pub fn with_error(path: PathBuf, error: String) -> Self {
        Self {
            path,
            bytes: None,
            error: Some(error),
        }
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn bytes(&self) -> Option<&[u8]> {
        self.bytes.as_deref()
    }

    pub fn into_bytes(self) -> Option<Vec<u8>> {
        self.bytes
    }

    pub fn error(&self) -> Option<&str> {
        self.error.as_deref()
    }
}

impl DirEntry {
    pub fn new(path: PathBuf, file_name: OsString, file_type: FsFileType) -> Self {
        Self {
            path,
            file_name,
            file_type,
        }
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn file_name(&self) -> &OsString {
        &self.file_name
    }

    pub fn file_type(&self) -> FsFileType {
        self.file_type
    }

    pub fn is_dir(&self) -> bool {
        matches!(self.file_type, FsFileType::Directory)
    }
}

#[async_trait(?Send)]
pub trait FsProvider: Send + Sync + 'static {
    fn open(&self, path: &Path, flags: &OpenFlags) -> io::Result<Box<dyn FileHandle>>;
    async fn open_async(&self, path: &Path, flags: &OpenFlags) -> io::Result<Box<dyn FileHandle>> {
        self.open(path, flags)
    }
    async fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
    async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()>;
    async fn remove_file(&self, path: &Path) -> io::Result<()>;
    async fn metadata(&self, path: &Path) -> io::Result<FsMetadata>;
    async fn symlink_metadata(&self, path: &Path) -> io::Result<FsMetadata>;
    async fn read_dir(&self, path: &Path) -> io::Result<Vec<DirEntry>>;
    async fn canonicalize(&self, path: &Path) -> io::Result<PathBuf>;
    async fn create_dir(&self, path: &Path) -> io::Result<()>;
    async fn create_dir_all(&self, path: &Path) -> io::Result<()>;
    async fn remove_dir(&self, path: &Path) -> io::Result<()>;
    async fn remove_dir_all(&self, path: &Path) -> io::Result<()>;
    async fn rename(&self, from: &Path, to: &Path) -> io::Result<()>;
    async fn set_readonly(&self, path: &Path, readonly: bool) -> io::Result<()>;

    async fn read_many(&self, paths: &[PathBuf]) -> io::Result<Vec<ReadManyEntry>> {
        let mut entries = Vec::with_capacity(paths.len());
        for path in paths {
            let entry = match self.read(path).await {
                Ok(payload) => ReadManyEntry::new(path.clone(), Some(payload)),
                Err(error) => {
                    warn!(
                        "fs.read_many.miss path={} kind={:?} error={}",
                        path.to_string_lossy(),
                        error.kind(),
                        error
                    );
                    ReadManyEntry::with_error(
                        path.clone(),
                        format!("kind={:?}; error={}", error.kind(), error),
                    )
                }
            };
            entries.push(entry);
        }
        Ok(entries)
    }

    async fn data_manifest_descriptor(
        &self,
        _request: &DataManifestRequest,
    ) -> io::Result<DataManifestDescriptor> {
        Err(io::Error::new(
            ErrorKind::Unsupported,
            "data manifest descriptor is unsupported by this provider",
        ))
    }

    async fn data_chunk_upload_targets(
        &self,
        _request: &DataChunkUploadRequest,
    ) -> io::Result<Vec<DataChunkUploadTarget>> {
        Err(io::Error::new(
            ErrorKind::Unsupported,
            "data chunk upload targets are unsupported by this provider",
        ))
    }

    async fn data_upload_chunk(
        &self,
        _target: &DataChunkUploadTarget,
        _data: &[u8],
    ) -> io::Result<()> {
        Err(io::Error::new(
            ErrorKind::Unsupported,
            "data chunk upload is unsupported by this provider",
        ))
    }
}

pub struct File {
    inner: Box<dyn FileHandle>,
}

impl File {
    fn from_handle(handle: Box<dyn FileHandle>) -> Self {
        Self { inner: handle }
    }

    pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
        let mut opts = OpenOptions::new();
        opts.read(true);
        opts.open(path)
    }

    pub async fn open_async(path: impl AsRef<Path>) -> io::Result<Self> {
        let mut opts = OpenOptions::new();
        opts.read(true);
        opts.open_async(path).await
    }

    pub fn create(path: impl AsRef<Path>) -> io::Result<Self> {
        let mut opts = OpenOptions::new();
        opts.write(true).create(true).truncate(true);
        opts.open(path)
    }

    pub async fn create_async(path: impl AsRef<Path>) -> io::Result<Self> {
        let mut opts = OpenOptions::new();
        opts.write(true).create(true).truncate(true);
        opts.open_async(path).await
    }

    pub async fn flush_async(&mut self) -> io::Result<()> {
        self.inner.flush_async().await
    }

    pub async fn sync_all_async(&mut self) -> io::Result<()> {
        self.inner.sync_all_async().await
    }
}

impl fmt::Debug for File {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("File").finish_non_exhaustive()
    }
}

impl Read for File {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.inner.read(buf)
    }
}

impl Write for File {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.inner.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.inner.flush()
    }
}

impl Seek for File {
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        self.inner.seek(pos)
    }
}

static PROVIDER: OnceCell<RwLock<Arc<dyn FsProvider>>> = OnceCell::new();
#[cfg(target_arch = "wasm32")]
static CURRENT_DIR: OnceCell<RwLock<PathBuf>> = OnceCell::new();

fn provider_lock() -> &'static RwLock<Arc<dyn FsProvider>> {
    PROVIDER.get_or_init(|| RwLock::new(default_provider()))
}

#[cfg(target_arch = "wasm32")]
fn current_dir_lock() -> &'static RwLock<PathBuf> {
    CURRENT_DIR.get_or_init(|| RwLock::new(PathBuf::from("/")))
}

fn with_provider<T>(f: impl FnOnce(&dyn FsProvider) -> T) -> T {
    let guard = provider_lock()
        .read()
        .expect("filesystem provider lock poisoned");
    f(&**guard)
}

fn resolve_path(path: &Path) -> PathBuf {
    #[cfg(target_arch = "wasm32")]
    {
        if path.is_absolute() {
            return path.to_path_buf();
        }
        if let Ok(base) = current_dir() {
            return base.join(path);
        }
        PathBuf::from("/").join(path)
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        path.to_path_buf()
    }
}

pub fn set_provider(provider: Arc<dyn FsProvider>) {
    let mut guard = provider_lock()
        .write()
        .expect("filesystem provider lock poisoned");
    *guard = provider;
}

/// Temporarily replace the active provider and return a guard that restores the
/// previous provider when dropped. Useful for tests that need to install a mock
/// filesystem without permanently mutating global state.
pub fn replace_provider(provider: Arc<dyn FsProvider>) -> ProviderGuard {
    let mut guard = provider_lock()
        .write()
        .expect("filesystem provider lock poisoned");
    let previous = guard.clone();
    *guard = provider;
    ProviderGuard { previous }
}

/// Run a closure with the supplied provider installed, restoring the previous
/// provider automatically afterwards.
pub fn with_provider_override<R>(provider: Arc<dyn FsProvider>, f: impl FnOnce() -> R) -> R {
    let guard = replace_provider(provider);
    let result = f();
    drop(guard);
    result
}

/// Returns the currently installed provider.
pub fn current_provider() -> Arc<dyn FsProvider> {
    provider_lock()
        .read()
        .expect("filesystem provider lock poisoned")
        .clone()
}

pub fn current_dir() -> io::Result<PathBuf> {
    #[cfg(target_arch = "wasm32")]
    {
        return Ok(current_dir_lock()
            .read()
            .expect("filesystem current dir lock poisoned")
            .clone());
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        std::env::current_dir()
    }
}

pub fn set_current_dir(path: impl AsRef<Path>) -> io::Result<()> {
    #[cfg(target_arch = "wasm32")]
    {
        let mut target = PathBuf::from(path.as_ref());
        if !target.is_absolute() {
            let base = current_dir()?;
            target = base.join(target);
        }
        let canonical =
            futures::executor::block_on(canonicalize_async(&target)).unwrap_or(target.clone());
        let metadata = futures::executor::block_on(metadata_async(&canonical))?;
        if !metadata.is_dir() {
            return Err(io::Error::new(
                ErrorKind::NotFound,
                format!("Not a directory: {}", canonical.display()),
            ));
        }
        let mut guard = current_dir_lock()
            .write()
            .expect("filesystem current dir lock poisoned");
        *guard = canonical;
        Ok(())
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        std::env::set_current_dir(path)
    }
}

pub struct ProviderGuard {
    previous: Arc<dyn FsProvider>,
}

impl Drop for ProviderGuard {
    fn drop(&mut self) {
        set_provider(self.previous.clone());
    }
}

pub async fn read_many_async(paths: &[PathBuf]) -> io::Result<Vec<ReadManyEntry>> {
    let resolved = paths
        .iter()
        .map(|path| resolve_path(path.as_path()))
        .collect::<Vec<_>>();
    let provider = current_provider();
    provider.read_many(&resolved).await
}

pub async fn read_async(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.read(&resolved).await
}

pub async fn read_to_string_async(path: impl AsRef<Path>) -> io::Result<String> {
    let bytes = read_async(path).await?;
    String::from_utf8(bytes).map_err(|err| io::Error::new(ErrorKind::InvalidData, err.utf8_error()))
}

pub async fn write_async(path: impl AsRef<Path>, data: impl AsRef<[u8]>) -> io::Result<()> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.write(&resolved, data.as_ref()).await
}

pub async fn remove_file_async(path: impl AsRef<Path>) -> io::Result<()> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.remove_file(&resolved).await
}

pub async fn metadata_async(path: impl AsRef<Path>) -> io::Result<FsMetadata> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.metadata(&resolved).await
}

pub async fn symlink_metadata_async(path: impl AsRef<Path>) -> io::Result<FsMetadata> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.symlink_metadata(&resolved).await
}

pub async fn read_dir_async(path: impl AsRef<Path>) -> io::Result<Vec<DirEntry>> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.read_dir(&resolved).await
}

pub async fn canonicalize_async(path: impl AsRef<Path>) -> io::Result<PathBuf> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.canonicalize(&resolved).await
}

pub async fn create_dir_async(path: impl AsRef<Path>) -> io::Result<()> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.create_dir(&resolved).await
}

pub async fn create_dir_all_async(path: impl AsRef<Path>) -> io::Result<()> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.create_dir_all(&resolved).await
}

pub async fn remove_dir_async(path: impl AsRef<Path>) -> io::Result<()> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.remove_dir(&resolved).await
}

pub async fn remove_dir_all_async(path: impl AsRef<Path>) -> io::Result<()> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.remove_dir_all(&resolved).await
}

pub async fn rename_async(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
    let resolved_from = resolve_path(from.as_ref());
    let resolved_to = resolve_path(to.as_ref());
    let provider = current_provider();
    provider.rename(&resolved_from, &resolved_to).await
}

pub async fn set_readonly_async(path: impl AsRef<Path>, readonly: bool) -> io::Result<()> {
    let resolved = resolve_path(path.as_ref());
    let provider = current_provider();
    provider.set_readonly(&resolved, readonly).await
}

pub async fn data_manifest_descriptor_async(
    request: &DataManifestRequest,
) -> io::Result<DataManifestDescriptor> {
    let provider = current_provider();
    provider.data_manifest_descriptor(request).await
}

pub async fn data_chunk_upload_targets_async(
    request: &DataChunkUploadRequest,
) -> io::Result<Vec<DataChunkUploadTarget>> {
    let provider = current_provider();
    provider.data_chunk_upload_targets(request).await
}

pub async fn data_upload_chunk_async(
    target: &DataChunkUploadTarget,
    data: &[u8],
) -> io::Result<()> {
    let provider = current_provider();
    provider.data_upload_chunk(target, data).await
}

/// Copy a file from `from` to `to`, truncating the destination when it exists.
/// Returns the number of bytes written, matching `std::fs::copy`.
pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<u64> {
    let mut reader = OpenOptions::new().read(true).open(from.as_ref())?;
    let mut writer = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(to.as_ref())?;
    io::copy(&mut reader, &mut writer)
}

fn default_provider() -> Arc<dyn FsProvider> {
    #[cfg(not(target_arch = "wasm32"))]
    {
        Arc::new(NativeFsProvider)
    }
    #[cfg(target_arch = "wasm32")]
    {
        Arc::new(PlaceholderFsProvider)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use once_cell::sync::Lazy;
    use std::io::{Read, Seek, SeekFrom, Write};
    use std::sync::Mutex;
    use tempfile::tempdir;

    static TEST_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

    struct UnsupportedProvider;

    struct AsyncOpenProvider {
        opened_async: Arc<Mutex<bool>>,
        flushed_async: Arc<Mutex<bool>>,
    }

    struct AsyncTestHandle {
        cursor: usize,
        data: Vec<u8>,
        flushed_async: Arc<Mutex<bool>>,
    }

    impl Read for AsyncTestHandle {
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
            let remaining = self.data.len().saturating_sub(self.cursor);
            let to_read = remaining.min(buf.len());
            buf[..to_read].copy_from_slice(&self.data[self.cursor..self.cursor + to_read]);
            self.cursor += to_read;
            Ok(to_read)
        }
    }

    impl Write for AsyncTestHandle {
        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
            let end = self.cursor + buf.len();
            if end > self.data.len() {
                self.data.resize(end, 0);
            }
            self.data[self.cursor..end].copy_from_slice(buf);
            self.cursor = end;
            Ok(buf.len())
        }

        fn flush(&mut self) -> io::Result<()> {
            Ok(())
        }
    }

    impl Seek for AsyncTestHandle {
        fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
            let next = match pos {
                SeekFrom::Start(offset) => offset as i64,
                SeekFrom::End(offset) => self.data.len() as i64 + offset,
                SeekFrom::Current(offset) => self.cursor as i64 + offset,
            };
            if next < 0 {
                return Err(io::Error::new(ErrorKind::InvalidInput, "seek before start"));
            }
            self.cursor = next as usize;
            Ok(self.cursor as u64)
        }
    }

    #[async_trait(?Send)]
    impl FileHandle for AsyncTestHandle {
        async fn flush_async(&mut self) -> io::Result<()> {
            *self.flushed_async.lock().unwrap() = true;
            Ok(())
        }
    }

    #[async_trait(?Send)]
    impl FsProvider for UnsupportedProvider {
        fn open(&self, _path: &Path, _flags: &OpenFlags) -> io::Result<Box<dyn FileHandle>> {
            Err(unsupported())
        }

        async fn read(&self, _path: &Path) -> io::Result<Vec<u8>> {
            Err(unsupported())
        }

        async fn write(&self, _path: &Path, _data: &[u8]) -> io::Result<()> {
            Err(unsupported())
        }

        async fn remove_file(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn metadata(&self, _path: &Path) -> io::Result<FsMetadata> {
            Err(unsupported())
        }

        async fn symlink_metadata(&self, _path: &Path) -> io::Result<FsMetadata> {
            Err(unsupported())
        }

        async fn read_dir(&self, _path: &Path) -> io::Result<Vec<DirEntry>> {
            Err(unsupported())
        }

        async fn canonicalize(&self, _path: &Path) -> io::Result<PathBuf> {
            Err(unsupported())
        }

        async fn create_dir(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn create_dir_all(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn remove_dir(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn remove_dir_all(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn rename(&self, _from: &Path, _to: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn set_readonly(&self, _path: &Path, _readonly: bool) -> io::Result<()> {
            Err(unsupported())
        }

        async fn data_manifest_descriptor(
            &self,
            _request: &DataManifestRequest,
        ) -> io::Result<DataManifestDescriptor> {
            Err(unsupported())
        }

        async fn data_chunk_upload_targets(
            &self,
            _request: &DataChunkUploadRequest,
        ) -> io::Result<Vec<DataChunkUploadTarget>> {
            Err(unsupported())
        }

        async fn data_upload_chunk(
            &self,
            _target: &DataChunkUploadTarget,
            _data: &[u8],
        ) -> io::Result<()> {
            Err(unsupported())
        }
    }

    #[async_trait(?Send)]
    impl FsProvider for AsyncOpenProvider {
        fn open(&self, _path: &Path, _flags: &OpenFlags) -> io::Result<Box<dyn FileHandle>> {
            Err(unsupported())
        }

        async fn open_async(
            &self,
            _path: &Path,
            _flags: &OpenFlags,
        ) -> io::Result<Box<dyn FileHandle>> {
            *self.opened_async.lock().unwrap() = true;
            Ok(Box::new(AsyncTestHandle {
                cursor: 0,
                data: b"async contents".to_vec(),
                flushed_async: self.flushed_async.clone(),
            }))
        }

        async fn read(&self, _path: &Path) -> io::Result<Vec<u8>> {
            Err(unsupported())
        }

        async fn write(&self, _path: &Path, _data: &[u8]) -> io::Result<()> {
            Err(unsupported())
        }

        async fn remove_file(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn metadata(&self, _path: &Path) -> io::Result<FsMetadata> {
            Err(unsupported())
        }

        async fn symlink_metadata(&self, _path: &Path) -> io::Result<FsMetadata> {
            Err(unsupported())
        }

        async fn read_dir(&self, _path: &Path) -> io::Result<Vec<DirEntry>> {
            Err(unsupported())
        }

        async fn canonicalize(&self, _path: &Path) -> io::Result<PathBuf> {
            Err(unsupported())
        }

        async fn create_dir(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn create_dir_all(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn remove_dir(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn remove_dir_all(&self, _path: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn rename(&self, _from: &Path, _to: &Path) -> io::Result<()> {
            Err(unsupported())
        }

        async fn set_readonly(&self, _path: &Path, _readonly: bool) -> io::Result<()> {
            Err(unsupported())
        }
    }

    fn unsupported() -> io::Error {
        io::Error::new(ErrorKind::Unsupported, "unsupported in test provider")
    }

    #[test]
    fn copy_file_round_trip() {
        let _guard = TEST_LOCK.lock().unwrap();
        let dir = tempdir().expect("tempdir");
        let src = dir.path().join("src.bin");
        let dst = dir.path().join("dst.bin");
        {
            let mut file = std::fs::File::create(&src).expect("create src");
            file.write_all(b"hello filesystem").expect("write src");
        }

        copy_file(&src, &dst).expect("copy");
        let mut dst_file = File::open(&dst).expect("open dst");
        let mut contents = Vec::new();
        dst_file
            .read_to_end(&mut contents)
            .expect("read destination");
        assert_eq!(contents, b"hello filesystem");
    }

    #[test]
    fn set_readonly_flips_metadata_flag() {
        let _guard = TEST_LOCK.lock().unwrap();
        let dir = tempdir().expect("tempdir");
        let path = dir.path().join("flag.txt");
        futures::executor::block_on(write_async(&path, b"flag")).expect("write");

        futures::executor::block_on(set_readonly_async(&path, true)).expect("set readonly");
        let meta = futures::executor::block_on(metadata_async(&path)).expect("metadata");
        assert!(meta.is_readonly());

        futures::executor::block_on(set_readonly_async(&path, false)).expect("unset readonly");
        let meta = futures::executor::block_on(metadata_async(&path)).expect("metadata");
        assert!(!meta.is_readonly());
    }

    #[test]
    fn replace_provider_restores_previous() {
        let _guard = TEST_LOCK.lock().unwrap();
        let original = current_provider();
        let custom: Arc<dyn FsProvider> = Arc::new(UnsupportedProvider);
        {
            let _guard = replace_provider(custom.clone());
            let active = current_provider();
            assert!(Arc::ptr_eq(&active, &custom));
        }
        let final_provider = current_provider();
        assert!(Arc::ptr_eq(&final_provider, &original));
    }

    #[test]
    fn open_async_and_flush_async_use_provider_async_paths() {
        let _guard = TEST_LOCK.lock().unwrap();
        let opened_async = Arc::new(Mutex::new(false));
        let flushed_async = Arc::new(Mutex::new(false));
        let provider = Arc::new(AsyncOpenProvider {
            opened_async: opened_async.clone(),
            flushed_async: flushed_async.clone(),
        });
        let _provider_guard = replace_provider(provider);

        let mut file =
            futures::executor::block_on(OpenOptions::new().read(true).open_async("data.txt"))
                .expect("async open");
        let mut contents = String::new();
        file.read_to_string(&mut contents).expect("read contents");
        futures::executor::block_on(file.flush_async()).expect("async flush");

        assert_eq!(contents, "async contents");
        assert!(*opened_async.lock().unwrap());
        assert!(*flushed_async.lock().unwrap());
    }

    #[test]
    fn with_provider_restores_even_on_panic() {
        let _guard = TEST_LOCK.lock().unwrap();
        let original = current_provider();
        let custom: Arc<dyn FsProvider> = Arc::new(UnsupportedProvider);
        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            with_provider_override(custom.clone(), || {
                let active = current_provider();
                assert!(Arc::ptr_eq(&active, &custom));
                panic!("boom");
            })
        }));
        assert!(result.is_err());
        let final_provider = current_provider();
        assert!(Arc::ptr_eq(&final_provider, &original));
    }
}
