//! Filesystem storage backend
//!
//! Stores nodes as markdown files with YAML frontmatter

use std::fs;

use async_trait::async_trait;

use crate::error::{KbError, Result};
use crate::models::{Graph, Node};
use crate::storage::Storage;

/// Filesystem-based storage backend
///
/// Stores graphs as directory structures with markdown files.
/// Each node becomes a `.md` file with YAML frontmatter.
pub struct FilesystemStorage {
    base_path: std::path::PathBuf,
}

impl FilesystemStorage {
    /// Create a new filesystem storage at the given path
    #[must_use]
    pub fn new(base_path: std::path::PathBuf) -> Self {
        Self { base_path }
    }

    /// Get the path to a graph directory (with path traversal protection)
    fn graph_path(&self, graph_name: &str) -> std::path::PathBuf {
        // Prevent path traversal attacks - reject names with .. or slashes
        let safe_name = graph_name.replace("..", "_").replace(['/', '\\'], "_");
        self.base_path.join(safe_name)
    }

    /// Get the path to a node file (with path traversal protection)
    fn node_path(&self, graph_name: &str, node_id: &str) -> std::path::PathBuf {
        // Prevent path traversal - reject IDs with .. or slashes
        let safe_id = node_id.replace("..", "_").replace(['/', '\\'], "_");
        self.graph_path(graph_name).join(format!("{safe_id}.md"))
    }

    /// Serialize a node to markdown with YAML frontmatter
    fn serialize_node(node: &Node) -> Result<String> {
        let frontmatter =
            serde_yaml::to_string(node).map_err(|e| KbError::Serialization(e.to_string()))?;
        Ok(format!("---\n{frontmatter}---\n\n{}", node.content))
    }

    /// Parse markdown with YAML frontmatter into a Node
    fn deserialize_node(content: &str) -> Result<Node> {
        if !content.starts_with("---\n") {
            return Err(KbError::Frontmatter("Missing frontmatter".to_string()));
        }

        let parts: Vec<&str> = content.splitn(3, "---\n").collect();
        if parts.len() < 3 {
            return Err(KbError::Frontmatter(
                "Invalid frontmatter format".to_string(),
            ));
        }

        let yaml = parts[1];
        let body = parts[2];

        let mut node: Node =
            serde_yaml::from_str(yaml).map_err(|e| KbError::Frontmatter(e.to_string()))?;
        node.content = body.to_string();

        Ok(node)
    }
}

#[async_trait]
impl Storage for FilesystemStorage {
    async fn save_graph(&mut self, graph: &Graph) -> Result<()> {
        let graph_path = self.graph_path(&graph.name);
        fs::create_dir_all(&graph_path)
            .map_err(|e| KbError::Storage(format!("Failed to create graph directory: {e}")))?;

        for node in graph.nodes.values() {
            let node_path = self.node_path(&graph.name, &node.id);
            let content = Self::serialize_node(node)?;
            fs::write(&node_path, content)
                .map_err(|e| KbError::Storage(format!("Failed to write node file: {e}")))?;
        }

        Ok(())
    }

    async fn load_graph(&self, name: &str) -> Result<Graph> {
        let graph_path = self.graph_path(name);
        if !graph_path.exists() {
            return Err(KbError::Graph(format!("Graph not found: {name}")));
        }

        let mut graph = Graph::new(name.to_string());
        let entries = fs::read_dir(&graph_path)
            .map_err(|e| KbError::Storage(format!("Failed to read graph directory: {e}")))?;

        for entry in entries {
            let entry = entry.map_err(|e| KbError::Storage(e.to_string()))?;
            let path = entry.path();

            if path.extension().is_some_and(|ext| ext == "md") {
                let content = fs::read_to_string(&path)
                    .map_err(|e| KbError::Storage(format!("Failed to read node file: {e}")))?;
                let node = Self::deserialize_node(&content)?;
                graph.nodes.insert(node.id.clone(), node);
            }
        }

        Ok(graph)
    }

    async fn save_node(&mut self, node: &Node) -> Result<()> {
        let graph_name = node
            .project
            .clone()
            .unwrap_or_else(|| "default".to_string());
        let graph_path = self.graph_path(&graph_name);
        fs::create_dir_all(&graph_path)
            .map_err(|e| KbError::Storage(format!("Failed to create graph directory: {e}")))?;

        let node_path = self.node_path(&graph_name, &node.id);
        let content = Self::serialize_node(node)?;
        fs::write(&node_path, content)
            .map_err(|e| KbError::Storage(format!("Failed to write node file: {e}")))
    }

    async fn load_node(&self, graph_name: &str, node_id: &str) -> Result<Node> {
        let node_path = self.node_path(graph_name, node_id);
        if !node_path.exists() {
            return Err(KbError::NodeNotFound(format!("{graph_name}/{node_id}")));
        }

        let content = fs::read_to_string(&node_path)
            .map_err(|e| KbError::Storage(format!("Failed to read node file: {e}")))?;
        Self::deserialize_node(&content)
    }

    async fn delete_node(&mut self, graph_name: &str, node_id: &str) -> Result<()> {
        let node_path = self.node_path(graph_name, node_id);
        if !node_path.exists() {
            return Err(KbError::NodeNotFound(format!("{graph_name}/{node_id}")));
        }

        fs::remove_file(&node_path)
            .map_err(|e| KbError::Storage(format!("Failed to delete node file: {e}")))
    }

    async fn list_graphs(&self) -> Result<Vec<String>> {
        if !self.base_path.exists() {
            return Ok(Vec::new());
        }

        let mut graphs = Vec::new();
        let entries = fs::read_dir(&self.base_path)
            .map_err(|e| KbError::Storage(format!("Failed to read base path: {e}")))?;

        for entry in entries {
            let entry = entry.map_err(|e| KbError::Storage(e.to_string()))?;
            let path = entry.path();
            if path.is_dir() {
                if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
                    graphs.push(name.to_string());
                }
            }
        }

        Ok(graphs)
    }

    async fn list_nodes(&self, graph_name: &str, node_type: Option<&str>) -> Result<Vec<Node>> {
        let graph_path = self.graph_path(graph_name);
        if !graph_path.exists() {
            return Err(KbError::Graph(format!("Graph not found: {graph_name}")));
        }

        let mut nodes = Vec::new();
        let entries = fs::read_dir(&graph_path)
            .map_err(|e| KbError::Storage(format!("Failed to read graph directory: {e}")))?;

        for entry in entries {
            let entry = entry.map_err(|e| KbError::Storage(e.to_string()))?;
            let path = entry.path();

            if path.extension().is_some_and(|ext| ext == "md") {
                let content = fs::read_to_string(&path)
                    .map_err(|e| KbError::Storage(format!("Failed to read node file: {e}")))?;
                let node = Self::deserialize_node(&content)?;

                if let Some(filter_type) = node_type {
                    if node.node_type.to_string() == filter_type {
                        nodes.push(node);
                    }
                } else {
                    nodes.push(node);
                }
            }
        }

        Ok(nodes)
    }
}
