//! Foundation Discovery System
//!
//! This module provides PAP-compliant dynamic discovery of available pages and
//! components from the Rustelo foundation by scanning actual source files. No
//! hardcoded lists!
//!
//! ## PAP Compliance:
//! - Configuration-driven: Uses manifest for foundation path resolution
//! - Language-agnostic: Discovers available items dynamically
//! - No hardcoding: Source of truth is the actual foundation source code
//! - Single source of truth: Foundation crates/src directories
//!
//! ## Usage Context:
//! These commands are designed for Rustelo implementation projects, not for
//! running from within the Rustelo source code itself.

use std::collections::HashMap;
use std::path::{Path, PathBuf};

use quote::ToTokens;
use serde::{Deserialize, Serialize};
use syn::{File, Item, ItemFn, ItemMod, ItemStruct};

use crate::build::path_resolution::get_manifest_resolver;

/// A discovered foundation item (page or component)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FoundationItem {
    /// Name of the item (e.g., "HomePage", "BrandHeader")
    pub name: String,
    /// Type of the item
    pub item_type: FoundationItemType,
    /// Module path within the foundation crate
    pub module_path: String,
    /// File path where the item is defined
    pub file_path: PathBuf,
    /// Whether it's a Leptos component (has #[component] attribute)
    pub is_component: bool,
    /// Documentation comment if available
    pub documentation: Option<String>,
    /// Function signature or struct definition
    pub signature: String,
    /// Dependencies on other foundation items
    pub dependencies: Vec<String>,
}

/// Type of foundation item
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum FoundationItemType {
    /// A page component
    Page,
    /// A UI component
    Component,
    /// A utility function
    Utility,
    /// A type definition
    Type,
}

/// Foundation discovery result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FoundationDiscovery {
    /// All discovered pages
    pub pages: HashMap<String, FoundationItem>,
    /// All discovered components
    pub components: HashMap<String, FoundationItem>,
    /// All discovered utilities
    pub utilities: HashMap<String, FoundationItem>,
    /// All discovered types
    pub types: HashMap<String, FoundationItem>,
    /// Foundation root path used for discovery
    pub foundation_root: PathBuf,
    /// Timestamp of discovery
    pub discovered_at: String,
}

impl FoundationDiscovery {
    /// Create a new empty discovery result
    pub fn new(foundation_root: PathBuf) -> Self {
        Self {
            pages: HashMap::new(),
            components: HashMap::new(),
            utilities: HashMap::new(),
            types: HashMap::new(),
            foundation_root,
            discovered_at: chrono::Utc::now().to_rfc3339(),
        }
    }

    /// Get an item by name regardless of type
    pub fn get_item(&self, name: &str) -> Option<&FoundationItem> {
        self.pages
            .get(name)
            .or_else(|| self.components.get(name))
            .or_else(|| self.utilities.get(name))
            .or_else(|| self.types.get(name))
    }

    /// Get all items of a specific type
    pub fn get_items_by_type(&self, item_type: FoundationItemType) -> Vec<&FoundationItem> {
        match item_type {
            FoundationItemType::Page => self.pages.values().collect(),
            FoundationItemType::Component => self.components.values().collect(),
            FoundationItemType::Utility => self.utilities.values().collect(),
            FoundationItemType::Type => self.types.values().collect(),
        }
    }
}

/// Check if the current execution is from within Rustelo source code itself
/// These commands are meant for implementation projects, not the Rustelo source
pub fn is_running_from_rustelo_source() -> bool {
    let current_dir = match std::env::current_dir() {
        Ok(dir) => dir,
        Err(_) => return false,
    };

    let current_str = current_dir.to_string_lossy().to_lowercase();

    // Check for key indicators we're in Rustelo source
    if current_str.contains("rustelo/crates/framework")
        || current_str.contains("rustelo/crates/foundation")
        || current_str.contains("rustelo_cli")
    {
        return true;
    }

    // Check for framework manifest file (indicates Rustelo source)
    if current_dir.join("FRAMEWORK_MANIFEST.toml").exists() {
        return true;
    }

    // Walk up directories looking for Rustelo source indicators
    let mut check_dir = current_dir.as_path();
    for _ in 0..10 {
        // Check for key Rustelo source structure
        if check_dir.join("FRAMEWORK_MANIFEST.toml").exists()
            || check_dir
                .join("crates/framework/crates/rustelo_cli")
                .exists()
        {
            return true;
        }

        if let Some(parent) = check_dir.parent() {
            check_dir = parent;
        } else {
            break;
        }
    }

    false
}

/// Get a helpful message when running from Rustelo source
pub fn get_source_usage_warning() -> String {
    r#"⚠️  Foundation commands are for implementation projects only!

You appear to be running this from the Rustelo source code itself.
These commands are designed to discover foundation items in projects
that USE Rustelo, not within Rustelo's own source code.

To use these commands:
1. Navigate to your Rustelo implementation project
2. Ensure it has a rustelo.manifest.toml file
3. Run the command from there

Example:
  cd ~/my-rustelo-website
  cargo rustelo fn list

If you're a framework developer testing discovery, use:
  cargo rustelo fn list --force-in-source"#
        .to_string()
}

/// Discover all available foundation pages and components by scanning source
/// files Uses smart caching to avoid re-scanning when foundation hasn't changed
pub fn discover_foundation_items() -> Result<FoundationDiscovery, Box<dyn std::error::Error>> {
    let manifest = get_manifest_resolver().map_err(|e| {
        if is_running_from_rustelo_source() {
            format!(
                "Cannot find rustelo.manifest.toml - You're running from Rustelo \
                 source!\n\n{}\n\nOriginal error: {}",
                get_source_usage_warning(),
                e
            )
        } else {
            format!(
                "Cannot find rustelo.manifest.toml in current directory tree.\n\nThis command \
                 requires a Rustelo implementation project with a manifest file.\n\nTo create a \
                 manifest file:\n1. Ensure you're in your Rustelo project root\n2. Create \
                 rustelo.manifest.toml with your project configuration\n\nExample minimal \
                 manifest:\n[project]\nname = \"my-rustelo-app\"\nversion = \
                 \"0.1.0\"\n\n[paths]\ncontent = \"content\"\nconfig = \"config\"\n\nOriginal \
                 error: {}",
                e
            )
        }
    })?;

    // Get foundation path from manifest - PAP compliant!
    let foundation_root = find_foundation_root(manifest.root())?;

    // Check if we can use cached discovery results
    if let Ok(cache) = crate::smart_cache::SmartCache::new("foundation-discovery") {
        let discovery_hash = calculate_foundation_hash(&foundation_root)?;

        if cache.is_foundation_cache_fresh(&discovery_hash)? {
            // Load from cache
            let pages = cache.load_foundation_pages()?;
            let components = cache.load_foundation_components()?;

            let mut discovery = FoundationDiscovery::new(foundation_root);

            // Convert Vec back to HashMap for compatibility
            for page in pages {
                discovery.pages.insert(page.name.clone(), page);
            }
            for component in components {
                discovery
                    .components
                    .insert(component.name.clone(), component);
            }

            println!(
                "cargo:warning=Using cached foundation discovery: {} pages, {} components",
                discovery.pages.len(),
                discovery.components.len()
            );

            return Ok(discovery);
        }
    }

    // Cache miss or stale - perform fresh discovery
    let mut discovery = FoundationDiscovery::new(foundation_root.clone());

    println!(
        "cargo:warning=Discovering foundation items from: {}",
        foundation_root.display()
    );

    // Discover pages from rustelo_pages crate
    if let Ok(pages_path) = find_crate_src(&foundation_root, "rustelo_pages_leptos") {
        discover_items_in_crate(
            &pages_path,
            "rustelo_pages_leptos",
            FoundationItemType::Page,
            &mut discovery,
        )?;
    }

    // Discover components from rustelo_components_leptos crate
    if let Ok(components_path) = find_crate_src(&foundation_root, "rustelo_components_leptos") {
        discover_items_in_crate(
            &components_path,
            "rustelo_components_leptos",
            FoundationItemType::Component,
            &mut discovery,
        )?;
    }

    // Discover utilities from core libraries
    if let Ok(core_lib_path) = find_crate_src(&foundation_root, "rustelo_core_lib") {
        discover_items_in_crate(
            &core_lib_path,
            "rustelo_core_lib",
            FoundationItemType::Utility,
            &mut discovery,
        )?;
    }

    if let Ok(core_types_path) = find_crate_src(&foundation_root, "rustelo_core_types") {
        discover_items_in_crate(
            &core_types_path,
            "rustelo_core_types",
            FoundationItemType::Type,
            &mut discovery,
        )?;
    }

    println!(
        "cargo:warning=Foundation discovery complete: {} pages, {} components, {} utilities, {} \
         types",
        discovery.pages.len(),
        discovery.components.len(),
        discovery.utilities.len(),
        discovery.types.len()
    );

    // Cache the results if possible
    if let Ok(cache) = crate::smart_cache::SmartCache::new("foundation-discovery") {
        let pages: Vec<_> = discovery.pages.values().cloned().collect();
        let components: Vec<_> = discovery.components.values().cloned().collect();

        if let Err(e) = cache.cache_foundation_pages(&pages) {
            println!("cargo:warning=Failed to cache foundation pages: {}", e);
        }
        if let Err(e) = cache.cache_foundation_components(&components) {
            println!("cargo:warning=Failed to cache foundation components: {}", e);
        }
    }

    Ok(discovery)
}

/// Find the foundation root directory by looking for foundation crates
fn find_foundation_root(manifest_root: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
    // Look for foundation in common locations relative to manifest
    let possible_paths = vec![
        manifest_root.join("rustelo/crates/foundation"),
        manifest_root.join("../rustelo/crates/foundation"),
        manifest_root.join("../../rustelo/crates/foundation"),
        manifest_root.join("crates/foundation"), // If we're inside rustelo
    ];

    for path in possible_paths {
        if path.exists() && path.join("crates").exists() {
            return Ok(path);
        }
    }

    Err("Foundation root not found - ensure rustelo foundation is accessible".into())
}

/// Find the src directory of a specific foundation crate
fn find_crate_src(
    foundation_root: &Path,
    crate_name: &str,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
    let crate_path = foundation_root.join("crates").join(crate_name).join("src");

    if crate_path.exists() {
        Ok(crate_path)
    } else {
        Err(format!(
            "Foundation crate '{}' not found at: {}",
            crate_name,
            crate_path.display()
        )
        .into())
    }
}

/// Discover items in a specific crate by scanning source files
fn discover_items_in_crate(
    src_path: &Path,
    crate_name: &str,
    default_type: FoundationItemType,
    discovery: &mut FoundationDiscovery,
) -> Result<(), Box<dyn std::error::Error>> {
    discover_items_recursive(src_path, src_path, crate_name, default_type, discovery)
}

/// Recursively discover items in source files
fn discover_items_recursive(
    current_path: &Path,
    src_root: &Path,
    crate_name: &str,
    default_type: FoundationItemType,
    discovery: &mut FoundationDiscovery,
) -> Result<(), Box<dyn std::error::Error>> {
    for entry in std::fs::read_dir(current_path)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            // Recurse into subdirectories
            discover_items_recursive(&path, src_root, crate_name, default_type.clone(), discovery)?;
        } else if path.extension().is_some_and(|ext| ext == "rs") {
            // Parse Rust source file
            if let Err(e) =
                discover_items_in_file(&path, src_root, crate_name, default_type.clone(), discovery)
            {
                // Log but don't fail on individual file parse errors
                println!("cargo:warning=Failed to parse {}: {}", path.display(), e);
            }
        }
    }

    Ok(())
}

/// Discover items in a single Rust source file
fn discover_items_in_file(
    file_path: &Path,
    src_root: &Path,
    crate_name: &str,
    default_type: FoundationItemType,
    discovery: &mut FoundationDiscovery,
) -> Result<(), Box<dyn std::error::Error>> {
    let content = std::fs::read_to_string(file_path)?;

    // Skip if file is empty or just comments
    if content.trim().is_empty()
        || content
            .lines()
            .all(|line| line.trim().starts_with("//") || line.trim().is_empty())
    {
        return Ok(());
    }

    // Parse the Rust source
    let parsed: File = syn::parse_file(&content)
        .map_err(|e| format!("Failed to parse {}: {}", file_path.display(), e))?;

    // Calculate module path from file path
    let module_path = calculate_module_path(file_path, src_root)?;

    // Extract items from the parsed file
    for item in parsed.items {
        if let Some(foundation_item) = extract_foundation_item(
            item,
            &module_path,
            file_path,
            crate_name,
            default_type.clone(),
        ) {
            // Add to appropriate collection
            match foundation_item.item_type {
                FoundationItemType::Page => {
                    discovery
                        .pages
                        .insert(foundation_item.name.clone(), foundation_item);
                }
                FoundationItemType::Component => {
                    discovery
                        .components
                        .insert(foundation_item.name.clone(), foundation_item);
                }
                FoundationItemType::Utility => {
                    discovery
                        .utilities
                        .insert(foundation_item.name.clone(), foundation_item);
                }
                FoundationItemType::Type => {
                    discovery
                        .types
                        .insert(foundation_item.name.clone(), foundation_item);
                }
            }
        }
    }

    Ok(())
}

/// Calculate module path from file path relative to src root
fn calculate_module_path(
    file_path: &Path,
    src_root: &Path,
) -> Result<String, Box<dyn std::error::Error>> {
    let relative = file_path.strip_prefix(src_root)?;

    let mut parts: Vec<String> = relative
        .parent()
        .unwrap_or(Path::new(""))
        .components()
        .filter_map(|c| c.as_os_str().to_str())
        .map(|s| s.to_string())
        .collect();

    // Add filename without .rs extension if not mod.rs or lib.rs
    if let Some(filename) = relative.file_stem().and_then(|s| s.to_str()) {
        if filename != "mod" && filename != "lib" {
            parts.push(filename.to_string());
        }
    }

    Ok(if parts.is_empty() {
        "crate".to_string()
    } else {
        parts.join("::")
    })
}

/// Extract a foundation item from a parsed syntax item
fn extract_foundation_item(
    item: Item,
    module_path: &str,
    file_path: &Path,
    crate_name: &str,
    default_type: FoundationItemType,
) -> Option<FoundationItem> {
    match item {
        Item::Fn(func) => {
            extract_function_item(func, module_path, file_path, crate_name, default_type)
        }
        Item::Struct(struct_item) => extract_struct_item(
            struct_item,
            module_path,
            file_path,
            crate_name,
            default_type,
        ),
        Item::Mod(mod_item) => {
            extract_mod_item(mod_item, module_path, file_path, crate_name, default_type)
        }
        _ => None, // Skip other item types for now
    }
}

/// Extract foundation item from a function
fn extract_function_item(
    func: ItemFn,
    module_path: &str,
    file_path: &Path,
    crate_name: &str,
    default_type: FoundationItemType,
) -> Option<FoundationItem> {
    let name = func.sig.ident.to_string();

    // Skip private functions
    let is_public = func.vis.to_token_stream().to_string().contains("pub");
    if !is_public {
        return None;
    }

    // Check if it's a Leptos component
    let is_component = func
        .attrs
        .iter()
        .any(|attr| attr.path().is_ident("component"));

    // Determine item type based on context
    let item_type = if is_component {
        match crate_name {
            "rustelo_pages_leptos" => FoundationItemType::Page,
            "rustelo_components_leptos" => FoundationItemType::Component,
            _ => default_type,
        }
    } else {
        FoundationItemType::Utility
    };

    // Extract documentation
    let documentation = extract_doc_comments(&func.attrs);

    // Create signature string
    let signature = format!("pub fn {}{}", name, quote::quote!(#func.sig));

    Some(FoundationItem {
        name,
        item_type,
        module_path: if module_path == "crate" {
            crate_name.to_string()
        } else {
            format!("{}::{}", crate_name, module_path)
        },
        file_path: file_path.to_path_buf(),
        is_component,
        documentation,
        signature,
        dependencies: Vec::new(), // TODO: Extract dependencies
    })
}

/// Extract foundation item from a struct
fn extract_struct_item(
    struct_item: ItemStruct,
    module_path: &str,
    file_path: &Path,
    crate_name: &str,
    _default_type: FoundationItemType,
) -> Option<FoundationItem> {
    let name = struct_item.ident.to_string();

    // Skip private structs
    let is_public = struct_item
        .vis
        .to_token_stream()
        .to_string()
        .contains("pub");
    if !is_public {
        return None;
    }

    // Extract documentation
    let documentation = extract_doc_comments(&struct_item.attrs);

    // Create signature string
    let signature = format!("pub struct {} {{ ... }}", name);

    Some(FoundationItem {
        name,
        item_type: _default_type,
        module_path: if module_path == "crate" {
            crate_name.to_string()
        } else {
            format!("{}::{}", crate_name, module_path)
        },
        file_path: file_path.to_path_buf(),
        is_component: false,
        documentation,
        signature,
        dependencies: Vec::new(), // TODO: Extract dependencies
    })
}

/// Extract foundation item from a module
fn extract_mod_item(
    mod_item: ItemMod,
    module_path: &str,
    file_path: &Path,
    crate_name: &str,
    default_type: FoundationItemType,
) -> Option<FoundationItem> {
    let name = mod_item.ident.to_string();

    // Skip private modules
    let is_public = mod_item.vis.to_token_stream().to_string().contains("pub");
    if !is_public {
        return None;
    }

    // Only include modules that re-export items (not just organizational)
    mod_item.content.as_ref()?;

    // Extract documentation
    let documentation = extract_doc_comments(&mod_item.attrs);

    Some(FoundationItem {
        name: name.clone(),
        item_type: default_type,
        module_path: if module_path == "crate" {
            crate_name.to_string()
        } else {
            format!("{}::{}", crate_name, module_path)
        },
        file_path: file_path.to_path_buf(),
        is_component: false,
        documentation,
        signature: format!("pub mod {}", name),
        dependencies: Vec::new(),
    })
}

/// Extract documentation comments from attributes
fn extract_doc_comments(attrs: &[syn::Attribute]) -> Option<String> {
    let doc_lines: Vec<String> = attrs
        .iter()
        .filter_map(|attr| {
            if attr.path().is_ident("doc") {
                if let syn::Meta::NameValue(meta) = &attr.meta {
                    if let syn::Expr::Lit(syn::ExprLit {
                        lit: syn::Lit::Str(lit_str),
                        ..
                    }) = &meta.value
                    {
                        return Some(lit_str.value().trim().to_string());
                    }
                }
            }
            None
        })
        .collect();

    if doc_lines.is_empty() {
        None
    } else {
        Some(doc_lines.join("\n"))
    }
}

/// Cache foundation discovery results to avoid repeated scanning
pub fn cache_foundation_discovery(
    discovery: &FoundationDiscovery,
) -> Result<(), Box<dyn std::error::Error>> {
    let manifest = get_manifest_resolver()?;
    let cache_dir = manifest
        .root()
        .join("target/site_build/devtools/build-cache/foundation");

    std::fs::create_dir_all(&cache_dir)?;

    let cache_file = cache_dir.join("discovery.json");
    let json = serde_json::to_string_pretty(discovery)?;
    std::fs::write(cache_file, json)?;

    println!("cargo:warning=Foundation discovery cached for future builds");

    Ok(())
}

/// Load cached foundation discovery if available and fresh
pub fn load_cached_foundation_discovery(
) -> Result<Option<FoundationDiscovery>, Box<dyn std::error::Error>> {
    let manifest = get_manifest_resolver()?;
    let cache_file = manifest
        .root()
        .join("target/site_build/devtools/build-cache/foundation/discovery.json");

    if !cache_file.exists() {
        return Ok(None);
    }

    let content = std::fs::read_to_string(cache_file)?;
    let discovery: FoundationDiscovery = serde_json::from_str(&content)?;

    // Check if foundation source is newer than cache
    if is_foundation_newer_than_cache(&discovery)? {
        return Ok(None); // Cache is stale
    }

    println!("cargo:warning=Using cached foundation discovery");
    Ok(Some(discovery))
}

/// Check if foundation source files are newer than cached discovery
fn is_foundation_newer_than_cache(
    discovery: &FoundationDiscovery,
) -> Result<bool, Box<dyn std::error::Error>> {
    let cache_time = chrono::DateTime::parse_from_rfc3339(&discovery.discovered_at)?;

    // Check if any foundation source file is newer than cache
    for crate_name in &[
        "rustelo_pages_leptos",
        "rustelo_components_leptos",
        "rustelo_core_lib",
        "rustelo_core_types",
    ] {
        if let Ok(src_path) = find_crate_src(&discovery.foundation_root, crate_name) {
            if is_directory_newer_than(&src_path, cache_time.naive_utc())? {
                return Ok(true);
            }
        }
    }

    Ok(false)
}

/// Check if any file in directory tree is newer than given time
fn is_directory_newer_than(
    dir: &Path,
    time: chrono::NaiveDateTime,
) -> Result<bool, Box<dyn std::error::Error>> {
    for entry in walkdir::WalkDir::new(dir) {
        let entry = entry?;
        if entry.file_type().is_file() {
            let metadata = entry.metadata()?;
            let modified = metadata.modified()?;
            let modified_naive = chrono::DateTime::<chrono::Utc>::from(modified).naive_utc();

            if modified_naive > time {
                return Ok(true);
            }
        }
    }

    Ok(false)
}

/// Calculate hash of foundation directory to detect changes
fn calculate_foundation_hash(foundation_root: &Path) -> Result<String, Box<dyn std::error::Error>> {
    use std::collections::BTreeSet;

    let mut file_hashes = BTreeSet::new();

    // Hash critical foundation source files
    let crate_names = [
        "rustelo_pages_leptos",
        "rustelo_components_leptos",
        "rustelo_core_lib",
        "rustelo_core_types",
    ];

    for crate_name in &crate_names {
        if let Ok(src_path) = find_crate_src(foundation_root, crate_name) {
            // Recursively hash all .rs files in the crate
            for entry in walkdir::WalkDir::new(&src_path).into_iter().flatten() {
                let path = entry.path();
                if path.extension().is_some_and(|ext| ext == "rs") {
                    if let Ok(content) = std::fs::read_to_string(path) {
                        file_hashes.insert(crate::smart_cache::hash_content(&content));
                    }
                }
            }

            // Also hash the Cargo.toml to detect dependency changes
            let cargo_toml = src_path
                .parent()
                .map(|p| p.join("Cargo.toml"))
                .unwrap_or_else(|| src_path.join("../Cargo.toml"));

            if cargo_toml.exists() {
                if let Ok(content) = std::fs::read_to_string(&cargo_toml) {
                    file_hashes.insert(crate::smart_cache::hash_content(&content));
                }
            }
        }
    }

    let combined_hash = file_hashes.into_iter().collect::<Vec<_>>().join("|");
    Ok(crate::smart_cache::hash_content(&combined_hash))
}
