//! Guideline inheritance resolution
//!
//! Manages inheritance of guidelines from shared organizational standards
//! to project-local overrides, allowing teams to maintain base guidelines
//! while projects customize for their specific needs.

use std::collections::HashMap;
use std::path::Path;

use tracing::{debug, info};
use uuid::Uuid;

use crate::error::{KbError, Result};
use crate::models::{Node, NodeType};
use crate::parser::parse_frontmatter_map;

/// Resolve guidelines with inheritance from shared base to local project
///
/// Merges guidelines from a shared base directory with project-local
/// guidelines, allowing projects to override or extend organizational
/// standards.
///
/// # Arguments
///
/// * `base_path` - Path to shared guidelines directory
/// * `local_path` - Path to project-local guidelines directory
///
/// # Returns
///
/// Merged vector of guidelines with local overriding base
///
/// # Inheritance Rules
///
/// - Local guidelines with same ID override shared guidelines
/// - Shared guidelines without local override are included
/// - Priority: local > shared
///
/// # Errors
///
/// Returns error if directories cannot be read or guidelines are malformed
pub fn resolve_guidelines(base_path: &Path, local_path: &Path) -> Result<Vec<Node>> {
    info!(
        "Resolving guidelines: base={:?}, local={:?}",
        base_path, local_path
    );

    let mut guidelines: HashMap<String, Node> = HashMap::new();

    // Load shared guidelines first
    if base_path.exists() {
        debug!("Loading shared guidelines from {:?}", base_path);
        load_guidelines_from_dir(base_path, &mut guidelines)?;
    }

    // Load and override with local guidelines
    if local_path.exists() {
        debug!("Loading local guidelines from {:?}", local_path);
        load_guidelines_from_dir(local_path, &mut guidelines)?;
    }

    let result: Vec<Node> = guidelines.into_values().collect();
    debug!("Resolved {} guidelines total", result.len());
    Ok(result)
}

/// Load guidelines from a directory and merge into the map
fn load_guidelines_from_dir(dir: &Path, guidelines: &mut HashMap<String, Node>) -> Result<()> {
    if !dir.is_dir() {
        return Err(KbError::Parser(format!(
            "Guidelines directory not found: {}",
            dir.display()
        )));
    }

    // Read all markdown files in directory
    let entries = std::fs::read_dir(dir)
        .map_err(|e| KbError::Parser(format!("Failed to read directory: {e}")))?;

    for entry in entries {
        let entry = entry.map_err(|e| KbError::Parser(format!("Failed to read entry: {e}")))?;
        let path = entry.path();

        if path.is_file() && path.extension().is_some_and(|ext| ext == "md") {
            debug!("Loading guideline from: {:?}", path);

            match load_guideline(&path) {
                Ok(node) => {
                    guidelines.insert(node.id.clone(), node);
                }
                Err(e) => {
                    // Log error but continue with other files
                    debug!("Failed to load guideline {path:?}: {e}");
                }
            }
        }
    }

    Ok(())
}

/// Load a single guideline node from a markdown file
///
/// # Errors
///
/// Returns error if the file cannot be read or parsed
fn load_guideline(path: &Path) -> Result<Node> {
    let content = std::fs::read_to_string(path)
        .map_err(|e| KbError::Parser(format!("Failed to read file: {e}")))?;

    let frontmatter = parse_frontmatter_map(&content)?;

    // Extract node properties from frontmatter
    let title = frontmatter.get("title").cloned().unwrap_or_else(|| {
        path.file_stem()
            .unwrap_or_default()
            .to_string_lossy()
            .to_string()
    });

    let id = frontmatter
        .get("id")
        .cloned()
        .unwrap_or_else(|| Uuid::new_v4().to_string());

    // Extract body from content (everything after frontmatter block)
    let body = if let Some(second_delimiter) = content[3..].find("---") {
        content[second_delimiter + 6..].trim().to_string()
    } else {
        String::new()
    };

    let mut node = Node::new(NodeType::Guideline, title);
    node.id = id;
    node.content = body;

    // Parse tags if present
    if let Some(tags_str) = frontmatter.get("tags") {
        node.tags = tags_str
            .split(',')
            .map(|t| t.trim().to_string())
            .filter(|t| !t.is_empty())
            .collect();
    }

    Ok(node)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_resolve_guidelines_empty() {
        let base = std::path::Path::new("/nonexistent/base");
        let local = std::path::Path::new("/nonexistent/local");

        let result = resolve_guidelines(base, local).unwrap();
        assert_eq!(result.len(), 0);
    }
}
