use anyhow::{Result, anyhow, Context};
use std::io::Read;
use std::sync::Arc;
use regex::Regex;
use crate::types::{DocNode, DocNodeKind, EntryKind};
use crate::traits::{ContextStore, Transaction};

pub trait DocManager {
    /// Reload state based on the latest Commit
    fn reload(&mut self, commit_hash: &str) -> Result<()>;

    /// Get the current document tree outline
    fn get_outline(&self) -> Result<DocNode>;

    /// Read node content
    fn read_content(&self, path: &str) -> Result<String>;

    /// Write content (Upsert)
    fn write_content(&mut self, path: &str, content: &str) -> Result<()>;

    /// Insert subsection (Implies Promotion)
    fn insert_subsection(&mut self, parent_path: &str, name: &str, content: &str) -> Result<()>;
    
    /// Demote Composite to Leaf (Aggregation)
    fn demote(&mut self, path: &str) -> Result<()>;

    /// Commit changes
    fn save(&mut self, message: &str) -> Result<String>;
}

pub struct DocOS<S: ContextStore> {
    store: Arc<S>,
    req_id: String,
    commit_hash: String,
    transaction: Option<Box<dyn Transaction>>,
}

impl<S: ContextStore> DocOS<S> {
    pub fn new(store: Arc<S>, req_id: &str, commit_hash: &str) -> Self {
        Self {
            store,
            req_id: req_id.to_string(),
            commit_hash: commit_hash.to_string(),
            transaction: None,
        }
    }

    fn ensure_transaction(&mut self) -> Result<&mut Box<dyn Transaction>> {
        if self.transaction.is_none() {
            let tx = self.store.begin_transaction(&self.req_id, &self.commit_hash)?;
            self.transaction = Some(tx);
        }
        Ok(self.transaction.as_mut().unwrap())
    }

    fn is_leaf(&self, path: &str) -> Result<bool> {
        match self.store.read_file(&self.req_id, &self.commit_hash, path) {
            Ok(_) => Ok(true),
            Err(_) => Ok(false),
        }
    }

    fn is_composite(&self, path: &str) -> Result<bool> {
         match self.store.list_dir(&self.req_id, &self.commit_hash, path) {
            Ok(_) => Ok(true),
            Err(_) => Ok(false),
        }
    }

    /// Parse Markdown headers to find subsections
    fn parse_markdown_headers(&self, content: &str) -> Vec<DocNode> {
        let re = Regex::new(r"(?m)^(#{1,6})\s+(.+)").unwrap();
        let mut sections = Vec::new();

        for cap in re.captures_iter(content) {
            let _level = cap[1].len();
            let name = cap[2].trim().to_string();
            
            // Simplified logic: All headers are children of the file node
            // In a real rich outline, we would build a tree based on level.
            // For this MVP, we treat found sections as direct children in the outline view.
            sections.push(DocNode {
                name: name.clone(),
                path: "".to_string(), // Virtual path, no direct file address
                kind: DocNodeKind::Section,
                children: vec![],
            });
        }
        sections
    }

    fn build_node(&self, name: String, path: String, kind: DocNodeKind) -> Result<DocNode> {
        let mut node = DocNode {
            name,
            path: path.clone(),
            kind: kind.clone(),
            children: vec![],
        };

        match kind {
            DocNodeKind::Composite => {
                let entries = self.store.list_dir(&self.req_id, &self.commit_hash, &path)?;
                
                // 1. Process index.md first if exists (content of this composite node)
                let mut index_content = String::new();
                if let Ok(mut reader) = self.store.read_file(&self.req_id, &self.commit_hash, &format!("{}/index.md", path)) {
                    reader.read_to_string(&mut index_content).unwrap_or_default();
                    let sections = self.parse_markdown_headers(&index_content);
                    node.children.extend(sections);
                }

                // 2. Process children files/dirs
                let mut children_nodes = Vec::new();
                for entry in entries {
                    if entry.name == "index.md" || entry.name == "_meta.json" || entry.name.starts_with(".") {
                        continue;
                    }

                    let child_path = if path == "/" {
                        entry.name.clone()
                    } else {
                        format!("{}/{}", path, entry.name)
                    };

                    let child_kind = match entry.kind {
                        EntryKind::Dir => DocNodeKind::Composite,
                        EntryKind::File => DocNodeKind::Leaf,
                    };

                    let child_node = self.build_node(entry.name, child_path, child_kind)?;
                    children_nodes.push(child_node);
                }
                // Sort children by name (simple default)
                children_nodes.sort_by(|a, b| a.name.cmp(&b.name));
                node.children.extend(children_nodes);
            }
            DocNodeKind::Leaf => {
                // Parse content for sections
                if let Ok(mut reader) = self.store.read_file(&self.req_id, &self.commit_hash, &path) {
                    let mut content = String::new();
                    reader.read_to_string(&mut content).unwrap_or_default();
                    let sections = self.parse_markdown_headers(&content);
                    node.children.extend(sections);
                }
            }
            DocNodeKind::Section => {
                // Sections don't have children in this simplified view
            }
        }

        Ok(node)
    }
}

impl<S: ContextStore> DocManager for DocOS<S> {
    fn reload(&mut self, commit_hash: &str) -> Result<()> {
        self.commit_hash = commit_hash.to_string();
        self.transaction = None; 
        Ok(())
    }

    fn get_outline(&self) -> Result<DocNode> {
        self.build_node("Root".to_string(), "/".to_string(), DocNodeKind::Composite)
    }

    fn read_content(&self, path: &str) -> Result<String> {
        let target_path = if path == "/" {
            "index.md".to_string()
        } else if self.is_composite(path)? {
            format!("{}/index.md", path)
        } else {
            path.to_string()
        };

        let mut reader = self.store.read_file(&self.req_id, &self.commit_hash, &target_path)
            .context("Failed to read content")?;
        let mut content = String::new();
        reader.read_to_string(&mut content)?;
        Ok(content)
    }

    fn write_content(&mut self, path: &str, content: &str) -> Result<()> {
        let is_comp = self.is_composite(path)?;
        let target_path = if is_comp {
            format!("{}/index.md", path)
        } else {
            path.to_string()
        };

        let tx = self.ensure_transaction()?;
        tx.write(&target_path, content.as_bytes())?;
        Ok(())
    }

    fn insert_subsection(&mut self, parent_path: &str, name: &str, content: &str) -> Result<()> {
        let is_leaf = self.is_leaf(parent_path)?;
        let is_composite = self.is_composite(parent_path)?;

        if !is_leaf && !is_composite && parent_path != "/" {
             return Err(anyhow!("Parent path '{}' does not exist", parent_path));
        }

        if is_leaf {
            // Promote: Leaf -> Composite
            let old_content = self.read_content(parent_path)?;
            
            let tx = self.ensure_transaction()?;
            tx.remove(parent_path)?;
            
            let index_path = format!("{}/index.md", parent_path);
            tx.write(&index_path, old_content.as_bytes())?;
            
            let child_path = format!("{}/{}", parent_path, name);
            tx.write(&child_path, content.as_bytes())?;
            
        } else {
            let child_path = if parent_path == "/" {
                name.to_string()
            } else {
                format!("{}/{}", parent_path, name)
            };
            
            let tx = self.ensure_transaction()?;
            tx.write(&child_path, content.as_bytes())?;
        }

        Ok(())
    }

    fn demote(&mut self, path: &str) -> Result<()> {
        if !self.is_composite(path)? {
            return Err(anyhow!("Path '{}' is not a composite node (directory)", path));
        }
        if path == "/" {
             return Err(anyhow!("Cannot demote root"));
        }

        // 1. Read index.md (Main content)
        let mut main_content = String::new();
        if let Ok(content) = self.read_content(path) {
            main_content = content;
        }
        
        // Reading directory entries
        let entries = self.store.list_dir(&self.req_id, &self.commit_hash, path)?;
        
        // Sort entries to have deterministic order
        let mut sorted_entries = entries;
        sorted_entries.sort_by(|a, b| a.name.cmp(&b.name));

        let mut combined_content = main_content;

        // Iterate for content reading (Borrowing self immutably)
        for entry in &sorted_entries {
            if entry.name == "index.md" || entry.name == "_meta.json" || entry.name.starts_with(".") {
                continue;
            }

            let child_rel_path = format!("{}/{}", path, entry.name);
            let child_content = self.read_content(&child_rel_path)?;
            combined_content.push_str(&format!("\n\n# {}\n\n", entry.name));
            combined_content.push_str(&child_content);
        }
        
        // Get list of items to remove before starting transaction (to avoid double borrow)
        // We need a recursive list of paths to remove from git index.
        let paths_to_remove = self.collect_recursive_paths(path)?;

        let tx = self.ensure_transaction()?;

        // 3. Remove everything recursively
        for p in paths_to_remove {
            tx.remove(&p)?;
        }
        // Also remove the directory path itself (conceptually, or handled by git index cleanup)
        // In our simplified VGCS, remove(dir) is not enough if not empty.
        // But we just cleaned up recursively.

        // 4. Write new file
        tx.write(path, combined_content.as_bytes())?;

        Ok(())
    }

    fn save(&mut self, message: &str) -> Result<String> {
        if let Some(tx) = self.transaction.take() {
            let new_oid = tx.commit(message, "DocOS User")?;
            self.commit_hash = new_oid.clone();
            Ok(new_oid)
        } else {
            Ok(self.commit_hash.clone())
        }
    }
}

impl<S: ContextStore> DocOS<S> {
    // Helper: Collect paths recursively (reading from store, immutable self)
    fn collect_recursive_paths(&self, path: &str) -> Result<Vec<String>> {
        let mut paths = Vec::new();
        
        let entries = self.store.list_dir(&self.req_id, &self.commit_hash, path);
        if let Ok(entries) = entries {
            for entry in entries {
                 let child_path = format!("{}/{}", path, entry.name);
                 match entry.kind {
                     EntryKind::File => {
                         paths.push(child_path);
                     },
                     EntryKind::Dir => {
                         // Add children of dir first
                         let mut sub_paths = self.collect_recursive_paths(&child_path)?;
                         paths.append(&mut sub_paths);
                         // No need to remove dir itself in git, but we might track it?
                     }
                 }
            }
        }
        Ok(paths)
    }
}
