//! Ontology Commands - Embedded and Marketplace Ontology Management
//!
//! This module implements ontology management commands for the ggen CLI:
//! - `ggen ontology list --embedded` - List embedded ontologies
//! - `ggen ontology status <uri>` - Check ontology availability
//! - `ggen ontology info <uri>` - Display detailed metadata
//! - `ggen ontology search <domain>` - Search for domain ontologies
//! - `ggen ontology install <package>` - Install ontology package from marketplace
//! - `ggen ontology lock` - Create lock file for installed packages

use clap_noun_verb::Result as VerbResult;
use clap_noun_verb_macros::verb;
use ggen_core::ontology::{CoreOntologyBundle, OntologyLoader};
use ggen_core::validation::StandardOntology;
use serde::Serialize;
use std::collections::BTreeMap;
use std::str::FromStr;

// ============================================================================
// Output Types (all must derive Serialize for JSON output)
// ============================================================================

/// Output for the `ggen ontology list` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyListOutput {
    /// List of ontologies
    pub ontologies: Vec<OntologyListEntry>,
    /// Total count
    pub count: usize,
}

/// Single ontology entry for list output
#[derive(Debug, Clone, Serialize)]
pub struct OntologyListEntry {
    /// Ontology name
    pub name: String,
    /// Namespace URI
    pub namespace: String,
    /// Size in bytes
    pub size: usize,
}

/// Output for the `ggen ontology status` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyStatusOutput {
    /// URI being queried
    pub uri: String,
    /// Whether it's embedded in the core bundle
    pub embedded: bool,
    /// Location ("core-bundle", "filesystem", or "not-found")
    pub location: String,
    /// Size in bytes if found
    pub size: Option<usize>,
    /// Name if found
    pub name: Option<String>,
}

/// Output for the `ggen ontology info` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyInfoOutput {
    /// Ontology name
    pub name: String,
    /// Namespace URI
    pub namespace: String,
    /// Size in bytes
    pub size: usize,
    /// Whether embedded
    pub embedded: bool,
    /// Content hash if available
    pub hash: Option<String>,
    /// Metadata
    pub metadata: BTreeMap<String, String>,
}

/// Output for the `ggen ontology search` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologySearchOutput {
    /// Search query
    pub query: String,
    /// Results found
    pub results: Vec<OntologySearchResult>,
    /// Total count
    pub count: usize,
    /// Message (for placeholders)
    pub message: Option<String>,
}

/// Single search result entry
#[derive(Debug, Clone, Serialize)]
pub struct OntologySearchResult {
    /// Package name
    pub name: String,
    /// Brief description
    pub description: String,
    /// Domain category
    pub domain: String,
}

/// Output for the `ggen ontology install` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyInstallOutput {
    /// Package ID installed (e.g., "acme/base@1.2.3")
    pub package: String,
    /// Whether installation succeeded
    pub success: bool,
    /// Installation message
    pub message: String,
    /// Package size in bytes
    pub size_bytes: Option<u64>,
    /// SHA-256 digest of the package
    pub digest: Option<String>,
    /// Number of dependencies installed
    pub dependencies_count: usize,
}

/// Output for the `ggen ontology lock` command
#[derive(Debug, Clone, Serialize)]
pub struct OntologyLockOutput {
    /// Lock file path
    pub lock_file: String,
    /// Number of packages locked
    pub packages_count: usize,
    /// Total size in bytes
    pub total_size_bytes: u64,
    /// Message describing the lock operation
    pub message: String,
    /// List of locked packages
    pub packages: Vec<LockFileEntry>,
}

/// Entry in a lock file
#[derive(Debug, Clone, Serialize)]
pub struct LockFileEntry {
    /// Package ID
    pub id: String,
    /// Package version
    pub version: String,
    /// SHA-256 digest
    pub digest: String,
    /// Installed timestamp
    pub installed_at: String,
}

/// Output for the `ggen ontology namespaces` command
#[derive(Debug, Clone, Serialize)]
pub struct NamespacesListOutput {
    /// List of namespace URIs and their prefixes
    pub namespaces: Vec<NamespaceEntry>,
    /// Total count
    pub count: usize,
}

/// Single namespace entry
#[derive(Debug, Clone, Serialize)]
pub struct NamespaceEntry {
    /// Prefix (e.g. rdf, owl, schema)
    pub prefix: String,
    /// Full namespace URI
    pub uri: String,
    /// Source (e.g. bundled-standard, core-stub)
    pub source: String,
}

// ============================================================================
// Verb Functions (the actual CLI commands)
// ============================================================================

/// List all embedded ontologies in the core bundle.
///
/// Usage:
///   ggen ontology list
///   ggen ontology list --embedded
#[verb]
pub fn list(#[arg(default_value = "true")] embedded: bool) -> VerbResult<OntologyListOutput> {
    if !embedded {
        return Ok(OntologyListOutput {
            ontologies: vec![],
            count: 0,
        });
    }

    let ontologies = CoreOntologyBundle::all();
    let entries: Vec<OntologyListEntry> = ontologies
        .iter()
        .map(|ont| OntologyListEntry {
            name: ont.name.to_string(),
            namespace: ont.namespace.to_string(),
            size: ont.size,
        })
        .collect();

    let count = entries.len();
    Ok(OntologyListOutput {
        ontologies: entries,
        count,
    })
}

/// List all available namespaces currently known to ggen
///
/// Usage:
///   ggen ontology namespaces
#[verb]
pub fn namespaces() -> VerbResult<NamespacesListOutput> {
    let standard_ns = ggen_core::domain::ontology::get_standard_namespaces();
    let namespaces: Vec<NamespaceEntry> = standard_ns
        .into_iter()
        .map(|ns| NamespaceEntry {
            prefix: ns.prefix,
            uri: ns.uri,
            source: ns.source,
        })
        .collect();

    let count = namespaces.len();
    Ok(NamespacesListOutput { namespaces, count })
}

/// Check the status and availability of an ontology.
///
/// Usage:
///   ggen ontology status http://www.w3.org/1999/02/22-rdf-syntax-ns#
///   ggen ontology status <uri>
#[verb]
pub fn status(uri: String) -> VerbResult<OntologyStatusOutput> {
    let is_embedded = OntologyLoader::is_embedded(&uri);

    if is_embedded {
        // Get metadata from core bundle
        if let Some(metadata) = OntologyLoader::get_metadata(&uri) {
            return Ok(OntologyStatusOutput {
                uri: uri.clone(),
                embedded: true,
                location: "core-bundle".to_string(),
                size: Some(metadata.size),
                name: Some(metadata.name.to_string()),
            });
        }
    }

    // Check if it exists on the filesystem
    if let Some(_content) = OntologyLoader::load_content(&uri, std::path::Path::new(".")) {
        return Ok(OntologyStatusOutput {
            uri: uri.clone(),
            embedded: false,
            location: "filesystem".to_string(),
            size: None,
            name: None,
        });
    }

    // Not found
    Ok(OntologyStatusOutput {
        uri: uri.clone(),
        embedded: false,
        location: "not-found".to_string(),
        size: None,
        name: None,
    })
}

/// Display detailed metadata for an ontology.
///
/// Usage:
///   ggen ontology info http://www.w3.org/1999/02/22-rdf-syntax-ns#
///   ggen ontology info <uri>
#[verb]
pub fn info(uri: String) -> VerbResult<OntologyInfoOutput> {
    // Try to get metadata from core bundle
    if let Some(metadata) = OntologyLoader::get_metadata(&uri) {
        let mut metadata_map = BTreeMap::new();
        metadata_map.insert("content_length".to_string(), metadata.size.to_string());

        return Ok(OntologyInfoOutput {
            name: metadata.name.to_string(),
            namespace: uri.clone(),
            size: metadata.size,
            embedded: true,
            hash: None, // Hash could be computed if needed
            metadata: metadata_map,
        });
    }

    // Not found
    let mut metadata_map = BTreeMap::new();
    metadata_map.insert("status".to_string(), "not found".to_string());

    Ok(OntologyInfoOutput {
        name: "unknown".to_string(),
        namespace: uri.clone(),
        size: 0,
        embedded: false,
        hash: None,
        metadata: metadata_map,
    })
}

/// Search for ontologies by domain or keyword.
///
/// Usage:
///   ggen ontology search financial
///   ggen ontology search healthcare
///   ggen ontology search <domain>
///
/// Note: This is a placeholder for marketplace integration.
#[verb]
pub fn search(query: String) -> VerbResult<OntologySearchOutput> {
    // Placeholder: In Phase 4, this will query the marketplace
    let message = Some(format!(
        "Marketplace integration not yet available. Use 'ggen ontology list' to see embedded ontologies."
    ));

    Ok(OntologySearchOutput {
        query,
        results: vec![],
        count: 0,
        message,
    })
}

/// Install an ontology package from the marketplace.
///
/// Usage:
///   ggen ontology install acme/base@1.2.3
///   ggen ontology install <package>@<version>
///
/// This command:
/// 1. Parses the package@version format
/// 2. Fetches metadata from the marketplace registry
/// 3. Resolves dependencies
/// 4. Downloads and caches packages
/// 5. Updates the lock file
#[verb]
pub fn install(package: String) -> VerbResult<OntologyInstallOutput> {
    // Parse package@version format
    let parts: Vec<&str> = package.split('@').collect();
    if parts.len() != 2 {
        return Ok(OntologyInstallOutput {
            package: package.clone(),
            success: false,
            message: "Error: Package format must be 'id@version' (e.g., 'acme/base@1.2.3')"
                .to_string(),
            size_bytes: None,
            digest: None,
            dependencies_count: 0,
        });
    }

    let package_id = parts[0];
    let version = parts[1];

    // NOTE: Phase 4 implementation - this is a placeholder that shows the structure.
    // The actual marketplace client integration will be done with real HTTP calls (Chicago TDD)
    // in the full implementation using ggen_marketplace::MarketplaceClient.

    Ok(OntologyInstallOutput {
        package: package.clone(),
        success: true,
        message: format!(
            "Ontology package {}@{} installed successfully (Phase 4 placeholder)",
            package_id, version
        ),
        size_bytes: Some(1024 * 1024), // 1MB example
        digest: Some("abc123def456".to_string()),
        dependencies_count: 0,
    })
}

/// Create or update a lock file for installed ontologies.
///
/// Usage:
///   ggen ontology lock
///
/// This command:
/// 1. Scans installed packages or ggen.toml
/// 2. Computes SHA-256 digests for all packages
/// 3. Creates .ggen/lock file with deterministic entries
/// 4. Reports summary (count, total size, hashes)
#[verb]
pub fn lock() -> VerbResult<OntologyLockOutput> {
    // NOTE: Phase 4 implementation - this is a placeholder that shows the structure.
    // The actual lock file creation will be done with:
    // 1. Reading installed packages from cache or ggen.toml
    // 2. Computing SHA-256 hashes for each package
    // 3. Writing to .ggen/ontology.lock
    // 4. Deterministic ordering by package ID

    Ok(OntologyLockOutput {
        lock_file: ".ggen/ontology.lock".to_string(),
        packages_count: 0,
        total_size_bytes: 0,
        message: "Lock file created successfully (Phase 4 placeholder)".to_string(),
        packages: vec![],
    })
}

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

    #[test]
    fn test_namespaces_command() {
        let result = namespaces().expect("namespaces command failed");
        assert_eq!(
            result.count, 8,
            "Expected 8 unique namespaces, got: {:?}",
            result.namespaces
        );
        assert_eq!(result.namespaces.len(), 8);

        // Verify prefixes
        let prefixes: std::collections::HashSet<&str> = result
            .namespaces
            .iter()
            .map(|ns| ns.prefix.as_str())
            .collect();
        let expected = [
            "rdf", "rdfs", "owl", "schema", "foaf", "dc", "skos", "bigfive",
        ];
        for p in &expected {
            assert!(prefixes.contains(p), "Missing prefix: {}", p);
        }

        // Verify some URIs and sources
        for ns in &result.namespaces {
            match ns.prefix.as_str() {
                "foaf" => {
                    assert_eq!(ns.uri, "http://xmlns.com/foaf/0.1/");
                    assert_eq!(ns.source, "bundled-standard");
                }
                "dc" => {
                    assert_eq!(ns.uri, "http://purl.org/dc/elements/1.1/");
                    assert_eq!(ns.source, "bundled-standard");
                }
                "rdf" => {
                    assert_eq!(ns.uri, "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
                    assert_eq!(ns.source, "bundled-standard");
                }
                _ => {}
            }
        }
    }
}
