//! Temporary tidalDB data directory for tests.
//!
//! Provides [`TempTidalHome`], a uniquely-named temporary directory that
//! is cleaned up on drop (unless `preserve` is set). This ensures test
//! isolation: every test gets its own filesystem sandbox.

use std::path::{Path, PathBuf};

use super::paths::Paths;

/// A temporary tidalDB data directory for use in tests.
///
/// Automatically cleaned up when dropped, unless `preserve` is set.
/// Each instance gets a unique directory under the OS temp root,
/// ensuring test isolation.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), std::io::Error> {
/// use tidaldb::TempTidalHome;
///
/// let home = TempTidalHome::new()?;
/// let paths = home.paths();
/// paths.ensure_all()?;
/// // `home` drops here, removing the temporary directory.
/// # Ok(())
/// # }
/// ```
pub struct TempTidalHome {
    base: PathBuf,
    preserve: bool,
}

impl TempTidalHome {
    /// Create a new temporary directory with a unique name.
    ///
    /// The directory is created under the OS temp root with prefix `tidaldb-`.
    /// It will be deleted when this value is dropped.
    ///
    /// # Errors
    ///
    /// Returns `std::io::Error` if the temp directory cannot be created.
    pub fn new() -> Result<Self, std::io::Error> {
        let dir = tempfile::Builder::new()
            .prefix("tidaldb-")
            .tempdir()?
            .keep();
        Ok(Self {
            base: dir,
            preserve: false,
        })
    }

    /// Create a temporary directory that is NOT deleted on drop.
    ///
    /// Useful for debugging: the directory survives so you can inspect
    /// its contents after a test failure.
    ///
    /// # Errors
    ///
    /// Returns `std::io::Error` if the temp directory cannot be created.
    pub fn with_preserve() -> Result<Self, std::io::Error> {
        let dir = tempfile::Builder::new()
            .prefix("tidaldb-")
            .tempdir()?
            .keep();
        Ok(Self {
            base: dir,
            preserve: true,
        })
    }

    /// The root path of this temporary directory.
    #[must_use]
    pub fn path(&self) -> &Path {
        &self.base
    }

    /// Construct a [`Paths`] rooted at this temporary directory.
    #[must_use]
    pub fn paths(&self) -> Paths {
        Paths::new(self.base.clone())
    }
}

impl Drop for TempTidalHome {
    fn drop(&mut self) {
        if !self.preserve
            && let Err(e) = std::fs::remove_dir_all(&self.base)
        {
            tracing::warn!(
                path = %self.base.display(),
                error = %e,
                "failed to clean up TempTidalHome"
            );
        }
    }
}

impl std::fmt::Debug for TempTidalHome {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("TempTidalHome")
            .field("base", &self.base)
            .field("preserve", &self.preserve)
            .finish()
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;

    #[test]
    fn new_creates_directory() {
        let home = TempTidalHome::new().unwrap();
        assert!(home.path().exists());
        assert!(home.path().is_dir());
    }

    #[test]
    fn path_contains_tidaldb_prefix() {
        let home = TempTidalHome::new().unwrap();
        let name = home
            .path()
            .file_name()
            .unwrap()
            .to_string_lossy()
            .to_string();
        assert!(
            name.starts_with("tidaldb-"),
            "expected tidaldb- prefix, got: {name}"
        );
    }

    #[test]
    fn paths_returns_paths_rooted_at_base() {
        let home = TempTidalHome::new().unwrap();
        let paths = home.paths();
        assert_eq!(paths.base(), home.path());
    }

    #[test]
    fn drop_removes_directory() {
        let path = {
            let home = TempTidalHome::new().unwrap();
            let p = home.path().to_owned();
            // Write a file so the dir is non-empty
            std::fs::write(p.join("testfile"), b"data").unwrap();
            assert!(p.exists());
            p
            // home dropped here
        };
        assert!(!path.exists(), "directory should be removed after drop");
    }

    #[test]
    fn with_preserve_keeps_directory() {
        let path = {
            let home = TempTidalHome::with_preserve().unwrap();
            let p = home.path().to_owned();
            std::fs::write(p.join("testfile"), b"data").unwrap();
            p
            // home dropped here — but preserve=true
        };
        assert!(
            path.exists(),
            "directory should still exist after drop with preserve"
        );
        // Manual cleanup since preserve=true
        std::fs::remove_dir_all(&path).unwrap();
    }

    #[test]
    fn two_homes_have_different_paths() {
        let home1 = TempTidalHome::new().unwrap();
        let home2 = TempTidalHome::new().unwrap();
        assert_ne!(home1.path(), home2.path());
    }

    #[test]
    fn debug_output() {
        let home = TempTidalHome::new().unwrap();
        let debug = format!("{home:?}");
        assert!(debug.contains("TempTidalHome"));
        assert!(debug.contains("preserve"));
    }
}
