use std::io::{Read, Write};
use std::os::unix::prelude::OpenOptionsExt;
use std::path::PathBuf;
use wildland_corex::api::{SfoError, SfoResult, SpecialFileOperations, SpecialFileType};
#[derive(Clone)]
pub struct SfoForLocalFS {
    path: String,
}
impl SfoForLocalFS {
    pub fn new(sfo_path: &str) -> Self {
        let stub = SfoForLocalFS {
            path: sfo_path.to_owned(),
        };
        stub.ensure_file_dirs_are_created().unwrap();
        stub
    }
    fn ensure_file_dirs_are_created(&self) -> SfoResult<()> {
        let file_types = [
            SpecialFileType::Data,
            SpecialFileType::Cache,
            SpecialFileType::Temporary,
        ];
        file_types.into_iter().for_each(|ft| {
            if !self
                .get_special_file_path_from_type(&ft)
                .try_exists()
                .unwrap()
            {
                std::fs::create_dir_all(self.get_special_file_path_from_type(&ft))
                    .map_err(|e| SfoError::Generic(e.to_string()))
                    .unwrap();
            }
        });
        Ok(())
    }
    fn get_special_file_path_from_type(&self, file_type: &SpecialFileType) -> PathBuf {
        match file_type {
            SpecialFileType::Data => PathBuf::from(format!("{}/data", self.path)),
            SpecialFileType::Cache => PathBuf::from(format!("{}/cache", self.path)),
            SpecialFileType::Temporary => PathBuf::from(format!("{}/tmp", self.path)),
        }
    }
}
impl SpecialFileOperations for SfoForLocalFS {
    fn create_special_file(
        &mut self,
        file_name: String,
        file_type: SpecialFileType,
        input_stream: Option<Vec<u8>>,
    ) -> SfoResult<()> {
        let mut file_location = self.get_special_file_path_from_type(&file_type);
        std::fs::create_dir_all(&file_location).map_err(|e| SfoError::Generic(e.to_string()))?;
        file_location.push(&file_name);
        if file_location.try_exists().unwrap() {
            return Err(SfoError::FileExists);
        }
        let mut f = std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .mode(0o600)
            .open(&file_location)
            .map_err(|e| SfoError::Generic(e.to_string()))?;
        if let Some(stream) = input_stream {
            tracing::debug!(
                "Written {} bytes to {}",
                f.write(&stream).unwrap(),
                file_name
            );
        }
        Ok(())
    }
    fn remove_special_file(
        &mut self,
        file_name: String,
        file_type: SpecialFileType,
    ) -> SfoResult<()> {
        let mut file_path = self.get_special_file_path_from_type(&file_type);
        file_path.push(file_name);
        if !file_path.try_exists().unwrap() {
            Err(SfoError::FileNotFound)
        } else {
            std::fs::remove_file(file_path).map_err(|e| SfoError::Generic(e.to_string()))
        }
    }
    fn write_to_special_file(
        &self,
        file_name: String,
        file_type: SpecialFileType,
        contents: Vec<u8>,
        append: bool,
    ) -> SfoResult<()> {
        let mut file_path = self.get_special_file_path_from_type(&file_type);
        file_path.push(&file_name);
        if !file_path.try_exists().unwrap() {
            Err(SfoError::FileNotFound)
        } else {
            let mut f = std::fs::OpenOptions::new()
                .write(true)
                .truncate(!append)
                .append(append)
                .open(&file_path)
                .map_err(|e| SfoError::Generic(e.to_string()))?;
            tracing::debug!(
                "Written {} bytes to {}",
                f.write(&contents).unwrap(),
                file_name
            );
            Ok(())
        }
    }
    fn read_from_special_file(
        &self,
        file_name: String,
        file_type: SpecialFileType,
    ) -> SfoResult<Vec<u8>> {
        let mut file_path = self.get_special_file_path_from_type(&file_type);
        file_path.push(&file_name);
        if !file_path.try_exists().unwrap() {
            Err(SfoError::FileNotFound)
        } else {
            let mut f = std::fs::OpenOptions::new()
                .read(true)
                .open(&file_path)
                .map_err(|e| SfoError::Generic(e.to_string()))?;
            let mut buffer = vec![];
            tracing::debug!(
                "Read {} bytes from {}",
                f.read_to_end(&mut buffer)
                    .map_err(|e| SfoError::Generic(e.to_string()))?,
                file_name
            );
            Ok(buffer)
        }
    }
    fn get_special_file_path(
        &self,
        file_name: String,
        file_type: SpecialFileType,
    ) -> SfoResult<String> {
        let mut path = self.get_special_file_path_from_type(&file_type);
        path.push(file_name);
        let str_path = path.to_str().unwrap().to_string();
        Ok(str_path)
    }
}