use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};

use image::EncodableLayout;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::to_string;

use crate::filesystem::FileSystemError;

use super::FileSystem;

pub struct NativeFileSystem {
    root: PathBuf,
    index: Arc<RwLock<HashMap<super::Id, PathBuf>>>,
}

impl NativeFileSystem {
    /// Rebuild the entire index by scanning the filesystem
    fn rebuild_index(&self) -> Result<(), std::io::Error> {
        let mut index = HashMap::new();
        Self::scan_directory(&self.root, &mut index)?;

        if let Ok(mut idx) = self.index.write() {
            *idx = index;
        }
        Ok(())
    }

    /// Recursively scan a directory and populate the index
    fn scan_directory(
        dir: &Path,
        index: &mut HashMap<super::Id, PathBuf>,
    ) -> Result<(), std::io::Error> {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();

            if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
                // Try to parse the filename as a UUID (our Id format)
                if let Ok(uuid) = uuid::Uuid::parse_str(name) {
                    let id = super::Id(uuid.to_string());
                    index.insert(id, path.clone());
                }
            }

            if path.is_dir() {
                Self::scan_directory(&path, index)?;
            }
        }
        Ok(())
    }

    /// Add an entry to the index
    fn add_to_index(&self, id: super::Id, path: PathBuf) {
        if let Ok(mut index) = self.index.write() {
            index.insert(id, path);
        }
    }

    /// Remove an entry from the index
    fn remove_from_index(&self, id: &super::Id) {
        if let Ok(mut index) = self.index.write() {
            index.remove(id);
        }
    }

    /// Get path from index, with fallback to filesystem scan if not found
    fn find_path_by_id(&self, id: &super::Id) -> Option<PathBuf> {
        // First try the index
        if let Ok(index) = self.index.read() {
            if let Some(path) = index.get(id) {
                // Verify the file still exists
                if path.exists() {
                    return Some(path.clone());
                }
            }
        }

        // Fallback: scan filesystem and update index
        let target_name = id.to_string();
        let mut stack = vec![self.root.clone()];

        while let Some(dir) = stack.pop() {
            let entries = match fs::read_dir(&dir) {
                Ok(e) => e,
                Err(_) => continue,
            };
            for entry in entries.flatten() {
                let path = entry.path();
                if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
                    if name == target_name {
                        // Update index with found path
                        self.add_to_index(id.clone(), path.clone());
                        return Some(path);
                    }
                }
                if path.is_dir() {
                    stack.push(path);
                }
            }
        }

        None
    }

    /// Build a file tree recursively from a given path
    fn build_file_tree(&self, path: &Path) -> Result<super::FileTree, FileSystemError> {
        let name = path
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("")
            .to_string();

        let is_directory = path.is_dir();

        // Check if this path corresponds to an ID in our index
        let id = if let Ok(index) = self.index.read() {
            index
                .iter()
                .find(|(_, indexed_path)| indexed_path.as_path() == path)
                .map(|(id, _)| id.clone())
        } else {
            None
        };

        let mut children = Vec::new();

        if is_directory {
            for entry in fs::read_dir(path).map_err(|e| {
                FileSystemError::DirectoryNotFound(path.to_path_buf(), e.to_string())
            })? {
                let entry = entry.map_err(|e| {
                    FileSystemError::DirectoryNotFound(path.to_path_buf(), e.to_string())
                })?;
                let child_path = entry.path();
                match self.build_file_tree(&child_path) {
                    Ok(child_tree) => children.push(child_tree),
                    Err(e) => eprintln!(
                        "Warning: Failed to build tree for {}: {}",
                        child_path.display(),
                        e
                    ),
                }
            }
        }

        Ok(super::FileTree {
            name,
            path: path.to_path_buf(),
            is_directory,
            id,
            children,
        })
    }
}

impl FileSystem for NativeFileSystem {
    fn new(root: impl AsRef<Path>) -> Self {
        let fs = Self {
            root: root.as_ref().to_path_buf(),
            index: Arc::new(RwLock::new(HashMap::new())),
        };
        fs.rebuild_index().unwrap_or_else(|e| {
            eprintln!("Warning: Failed to build initial index: {e}");
        });
        fs
    }

    fn create(&self, directory: &Path, data: impl Serialize) -> Result<super::Id, FileSystemError> {
        let dir = if directory.is_absolute() {
            directory.to_path_buf()
        } else {
            self.root.join(directory)
        };

        if !dir.exists() {
            fs::create_dir_all(&dir).map_err(|e| {
                FileSystemError::DirectoryNotFound(dir.to_path_buf(), e.to_string())
            })?;
        }

        if !dir.is_dir() {
            return Err(FileSystemError::DirectoryNotFound(
                dir.to_path_buf(),
                "".to_string(),
            ));
        }

        let id = super::Id::new();
        let file_path = dir.join(id.to_string());

        let mut file = File::create(&file_path)
            .map_err(|e| FileSystemError::DirectoryNotFound(dir.to_path_buf(), e.to_string()))?;
        file.write_all(serde_json::to_string(&data).unwrap().as_bytes())
            .map_err(|e| FileSystemError::DirectoryNotFound(dir.to_path_buf(), e.to_string()))?;

        // Add to index
        self.add_to_index(id.clone(), file_path);

        Ok(id)
    }

    fn read<T: DeserializeOwned>(&self, id: &super::Id) -> Result<T, FileSystemError> {
        let path = self.find_path_by_id(id).ok_or_else(|| {
            FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
        })?;

        let val = serde_json::from_reader(
            File::open(path)
                .map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?,
        )
        .map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?;

        Ok(val)
    }

    fn write(&self, id: &super::Id, data: impl Serialize) -> Result<(), FileSystemError> {
        let path = self.find_path_by_id(id).ok_or_else(|| {
            FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
        })?;

        let mut file = File::create(path)
            .map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?;

        file.write_all(serde_json::to_string(&data).unwrap().as_bytes())
            .map_err(|_| FileSystemError::FileNotFound(id.clone(), "".to_string()))?;
        Ok(())
    }

    fn delete(&self, id: &super::Id) -> Result<(), FileSystemError> {
        let path = self.find_path_by_id(id).ok_or_else(|| {
            FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
        })?;

        let result = if path.is_dir() {
            fs::remove_dir_all(path)
        } else {
            fs::remove_file(path)
        };

        // Remove from index if deletion was successful
        if result.is_ok() {
            self.remove_from_index(id);
        }

        result.map_err(|e| FileSystemError::FileNotFound(id.clone(), e.to_string()))
    }

    fn clone(&self, id: &super::Id, new_id: &super::Id) -> Result<(), FileSystemError> {
        let src = self.find_path_by_id(id).ok_or_else(|| {
            FileSystemError::FileNotFound(id.clone(), "No path found!".to_string())
        })?;

        let parent = src.parent().map(Path::to_path_buf).ok_or_else(|| {
            FileSystemError::FileNotFound(id.clone(), "No parent found!".to_string())
        })?;

        let dst = parent.join(new_id.to_string());
        let result = if src.is_dir() {
            // Simple recursive dir copy
            fn copy_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
                fs::create_dir_all(dst)?;
                for entry in fs::read_dir(src)? {
                    let entry = entry?;
                    let ty = entry.file_type()?;
                    let sp = entry.path();
                    let dp = dst.join(entry.file_name());
                    if ty.is_dir() {
                        copy_dir(&sp, &dp)?;
                    } else if ty.is_file() {
                        fs::copy(&sp, &dp)?;
                    }
                }
                Ok(())
            }
            copy_dir(&src, &dst)
        } else {
            fs::copy(&src, &dst).map(|_| ())
        };

        // Add cloned file/directory to index if copy was successful
        if result.is_ok() {
            self.add_to_index(new_id.clone(), dst);
        }

        result.map_err(|e| FileSystemError::FileNotFound(id.clone(), e.to_string()))
    }

    fn exists(&self, id: &super::Id) -> bool {
        self.find_path_by_id(id).is_some()
    }

    fn lsdir(&self, id: &super::Id) -> Result<Vec<String>, FileSystemError> {
        let path = match self.find_path_by_id(id) {
            Some(p) if p.is_dir() => p,
            Some(p) => p.parent().map(Path::to_path_buf).ok_or_else(|| {
                FileSystemError::DirectoryNotFound(PathBuf::from(id.to_string()), "".to_string())
            })?,
            None => {
                return Err(FileSystemError::DirectoryNotFound(
                    PathBuf::from(id.to_string()),
                    id.to_string(),
                ));
            }
        };

        let mut entries = Vec::new();
        for entry in fs::read_dir(&path)
            .map_err(|e| FileSystemError::DirectoryNotFound(path.clone(), e.to_string()))?
        {
            let entry = entry
                .map_err(|e| FileSystemError::DirectoryNotFound(path.clone(), e.to_string()))?;
            if let Some(name) = entry.file_name().to_str() {
                entries.push(name.to_string());
            }
        }
        Ok(entries)
    }

    fn file_tree(&self, root_path: &Path) -> Result<super::FileTree, FileSystemError> {
        self.build_file_tree(root_path)
    }
}
