use std::{
    fs::{self, File, Metadata},
    io::{self, Write},
    os::fd::{AsRawFd, RawFd},
    path::{Path, PathBuf},
};

use runtime::rt as yggdrasil_rt;
use yggdrasil_rt::io::{FileMetadataUpdate, FileMetadataUpdateMode, FileMode as RawFileMode};

use crate::{
    fs::{FileMode, FileModeUpdate, MetadataExt},
    sys::TempFile,
};

pub struct TempFileImpl {
    file: Option<File>,
    path: PathBuf,
    persist: bool,
}

pub struct TempPathImpl;

impl TempFile for TempFileImpl {
    type TempPath = TempPathImpl;

    fn create(_pattern: &str) -> io::Result<Self> {
        todo!()
    }

    fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        let path = path.as_ref().to_owned();
        let file = File::create_new(&path)?;
        Ok(Self {
            file: Some(file),
            path,
            persist: false,
        })
    }

    fn persist(mut self) {
        self.persist = true;
    }

    fn rename<P: AsRef<Path>>(mut self, new_name: P) -> io::Result<()> {
        let _ = self.file.take();
        self.persist = true;
        fs::remove_file(&new_name).ok(); // TODO hacky
        fs::rename(&self.path, new_name)
    }

    fn into_temp_path(self) -> Self::TempPath {
        todo!()
    }

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

impl Drop for TempFileImpl {
    fn drop(&mut self) {
        let _ = self.file.take();
        if !self.persist {
            fs::remove_file(&self.path).ok();
        }
    }
}

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

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

impl AsRawFd for TempFileImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.file.as_ref().unwrap().as_raw_fd()
    }
}

impl AsRef<Path> for TempPathImpl {
    fn as_ref(&self) -> &Path {
        todo!()
    }
}

impl MetadataExt for Metadata {
    fn mode(&self) -> FileMode {
        use std::os::yggdrasil::fs::MetadataExt as ME;
        let mode = <Self as ME>::mode_ext(self).into_raw();
        FileMode::from(mode)
    }
}

pub fn update_file_mode<P: AsRef<Path>>(
    at: Option<RawFd>,
    path: P,
    mode: FileModeUpdate,
) -> io::Result<()> {
    // TODO make a soft error?
    let path_str = path.as_ref().to_str().expect("Path is not a valid string");
    let raw_update_mode = match mode {
        FileModeUpdate::Set(mode) => {
            let raw_file_mode = unsafe { RawFileMode::from_raw(mode.bits()) };
            FileMetadataUpdateMode::Set(raw_file_mode)
        }
        FileModeUpdate::Modify { set, clear } => {
            let raw_set_mode = unsafe { RawFileMode::from_raw(set.bits()) };
            let raw_clear_mode = unsafe { RawFileMode::from_raw(clear.bits()) };
            FileMetadataUpdateMode::Modify {
                set: raw_set_mode,
                clear: raw_clear_mode,
            }
        }
    };
    unsafe {
        yggdrasil_rt::sys::update_metadata(
            at,
            path_str,
            &FileMetadataUpdate::Permissions(raw_update_mode),
        )
        .map_err(io::Error::from)
    }
}
