use crate::config::LocalConfig;
use crate::{BlobStorage, BlobStorageError, ByteStream, File, FileReader, PART_SIZE};
use async_trait::async_trait;
use std::path::PathBuf;
use tokio::fs;
use tokio::io::BufReader;
use tokio::sync::mpsc;

#[derive(Clone)]
pub struct LocalStorage {
    base_path: PathBuf,
}

impl LocalStorage {
    pub fn new(config: &LocalConfig) -> Self {
        // Check if we have write permission on the base path
        let base_path = PathBuf::from(&config.storage_base_path);
        // Create the directory at base_path if it doesn't exist
        if let Err(e) = std::fs::create_dir_all(&base_path) {
            panic!(
                "Failed to create storage_base_path {}: {}",
                base_path.display(),
                e
            );
        }
        if let Err(e) = std::fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(base_path.join(".permission_check"))
        {
            panic!(
                "No write permission on storage_base_path {}: {}",
                base_path.display(),
                e
            );
        } else {
            // Clean up the permission check file if it was created
            let _ = std::fs::remove_file(base_path.join(".permission_check"));
        }
        LocalStorage {
            base_path: PathBuf::from(&config.storage_base_path),
        }
    }

    fn get_file_path(&self, key: &str) -> PathBuf {
        let key = if key.starts_with('/') { &key[1..] } else { key };
        self.base_path.join(PathBuf::from(key))
    }
}

#[async_trait]
impl BlobStorage for LocalStorage {
    async fn download_files(
        &self,
        keys: &[&str],
    ) -> Result<mpsc::Receiver<FileReader>, BlobStorageError> {
        let (tx, rx) = mpsc::channel(100);

        for key in keys {
            let file = self.download_file(key).await?;
            tx.send(file)
                .await
                .map_err(BlobStorageError::ChannelSendError)?;
        }

        Ok(rx)
    }

    async fn download_file(&self, key: &str) -> Result<FileReader, BlobStorageError> {
        let file_path = self.get_file_path(key);

        if !file_path.exists() {
            return Err(BlobStorageError::DownloadLocalError(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("File not found: {}", file_path.display()),
            )));
        }

        let data = fs::File::open(file_path).await?;

        Ok(FileReader {
            key: key.to_string(),
            data: Box::pin(BufReader::new(data)),
        })
    }

    async fn upload_file(&self, data: File) -> Result<Option<String>, BlobStorageError> {
        let file_path = self.get_file_path(&data.key);
        let mut file = if file_path.exists() {
            fs::OpenOptions::new()
                .write(true)
                .create(true)
                .append(false)
                .open(file_path)
                .await?
        } else {
            // Ensure parent directory exists
            if let Some(parent) = file_path.parent() {
                fs::create_dir_all(parent).await?;
            }
            fs::File::create(file_path).await?
        };
        tokio::io::copy(&mut data.data.as_ref(), &mut file).await?;
        Ok(None)
    }

    async fn upload_files(
        &self,
        mut files: mpsc::Receiver<File>,
    ) -> Result<Vec<String>, BlobStorageError> {
        let mut keys = Vec::new();
        while let Some(file) = files.recv().await {
            let key = self.upload_file(file).await?;
            if let Some(key) = key {
                keys.push(key);
            }
        }
        Ok(keys)
    }

    async fn delete_file(&self, key: &str) -> Result<(), BlobStorageError> {
        let file_path = self.get_file_path(key);
        if !file_path.exists() {
            return Err(BlobStorageError::DownloadLocalError(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("File not found: {}", file_path.display()),
            )));
        }
        fs::remove_file(file_path).await?;
        Ok(())
    }

    async fn stream_upload(
        &self,
        key: &str,
        mut part: ByteStream,
    ) -> Result<Option<String>, BlobStorageError> {
        use tokio::io::{AsyncReadExt, AsyncWriteExt};
        let file_path = self.get_file_path(key);
        let mut file = if file_path.exists() {
            fs::OpenOptions::new()
                .write(true)
                .append(true)
                .open(file_path)
                .await?
        } else {
            // Ensure parent directory exists
            if let Some(parent) = file_path.parent() {
                fs::create_dir_all(parent).await?;
            }
            fs::File::create(file_path).await?
        };
        loop {
            let mut buffer = vec![0u8; PART_SIZE];
            let n = part.read(&mut buffer).await?;
            if n == 0 {
                break; // EOF
            }
            let chunk = &buffer[..n];
            file.write_all(chunk).await?;
            file.flush().await?;
        }

        Ok(None)
    }
}
