//! fstab serialization and file writing.
//!
//! Provides `Display` implementations for `Fstab` and `Entry` with proper
//! escape encoding, plus safe file writing (overwrite and atomic via
//! tempfile + fsync + rename).

use crate::escape::encode_escapes;
use crate::types::{Entry, Fstab};
use std::fmt;
use std::io;
use std::path::Path;

/// Format an [`Fstab`] for display / serialization.
///
/// Outputs the intro comment (if any), each entry with its preceding
/// comment (if any), and the trailing comment (if any).
impl fmt::Display for Fstab {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(ref intro) = self.intro_comment {
            f.write_str(intro)?;
        }
        for entry in &self.entries {
            if let Some(ref comment) = entry.comment {
                f.write_str(comment)?;
            }
            write!(f, "{entry}")?;
        }
        if let Some(ref trailing) = self.trailing_comment {
            f.write_str(trailing)?;
        }
        Ok(())
    }
}

/// Format an [`Entry`] as an fstab data line.
///
/// The output includes the spec (with escape encoding), mount point
/// (with escape encoding), filesystem type, and options. If either
/// `freq` or `passno` is non-zero, both are included.
impl fmt::Display for Entry {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {} {} {}",
            encode_escapes(&self.spec.to_string()),
            encode_escapes(&self.file.to_string()),
            self.vfstype,
            self.options,
        )?;
        if self.freq != 0 || self.passno != 0 {
            write!(f, " {} {}", self.freq, self.passno)?;
        }
        writeln!(f)?;
        Ok(())
    }
}

impl Fstab {
    /// Write the fstab to a file, overwriting if it exists.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use mount_fstab::Fstab;
    /// let fstab = Fstab::new();
    /// fstab.write_to("/tmp/fstab").unwrap();
    /// ```
    ///
    /// # Errors
    ///
    /// Returns [`std::io::Error`] if the file cannot be written.
    pub fn write_to(&self, path: impl AsRef<Path>) -> Result<(), io::Error> {
        let content = self.to_string();
        std::fs::write(path, content)
    }

    /// Atomically write the fstab to a file using a temp file, fsync, and rename.
    ///
    /// This ensures the file is never in a partially-written state:
    /// 1. Write to a temporary file in the same directory
    /// 2. Call `sync_all()` to flush data to disk
    /// 3. Rename the temporary file over the target path (atomic on POSIX)
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use mount_fstab::Fstab;
    /// let fstab = Fstab::parse_str("UUID=root / ext4 defaults 0 1\n").unwrap();
    /// fstab.write_atomic("/etc/fstab").unwrap();
    /// ```
    ///
    /// # Errors
    ///
    /// Returns [`std::io::Error`] if the temp file cannot be created, written,
    /// synced, or renamed.
    pub fn write_atomic(&self, path: impl AsRef<Path>) -> Result<(), io::Error> {
        let path = path.as_ref();
        let parent = path.parent().unwrap_or(Path::new("."));
        let mut tmp = tempfile::NamedTempFile::new_in(parent)?;
        let content = self.to_string();
        io::Write::write_all(&mut tmp, content.as_bytes())?;
        tmp.as_file_mut().sync_all()?;
        tmp.persist(path).map_err(|e| e.error)?;
        Ok(())
    }
}
