use anyhow::Result;
use std::{
    fs::{File, OpenOptions},
    io::{Read, Seek, Write},
    os::unix::fs::PermissionsExt,
    path::Path,
};

#[allow(clippy::redundant_pub_crate)]
pub(crate) struct ReadOnlyGuard {
    is_readonly: bool,
    file: File,
}

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

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

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

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

impl ReadOnlyGuard {
    pub(crate) fn new(path: impl AsRef<Path>, truncate: bool) -> Result<Self> {
        let path = path.as_ref();

        let is_readonly = if path.exists() {
            let file = File::open(path)?;
            let mut permissions = file.metadata()?.permissions();
            let is_readonly = permissions.readonly();

            permissions.set_mode(permissions.mode() | 0o600);
            file.set_permissions(permissions)?;
            is_readonly
        } else {
            false
        };

        let file = OpenOptions::new()
            .truncate(truncate)
            .read(true)
            .write(true)
            .create(true)
            .open(path)?;

        Ok(Self { is_readonly, file })
    }
}

impl Drop for ReadOnlyGuard {
    fn drop(&mut self) {
        if !self.is_readonly {
            return;
        }

        let Ok(metadata) = self.file.metadata() else {
            return;
        };
        let mut permissions = metadata.permissions();
        permissions.set_readonly(true);
        self.file.set_permissions(permissions).ok();
    }
}
