//! Treefile parsing and validation for apt-ostree

use apt_ostree::lib::error::{AptOstreeError, AptOstreeResult};
use serde::{Deserialize, Serialize};

/// Treefile structure for apt-ostree composition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Treefile {
    /// API version
    pub api_version: String,
    /// Kind of tree
    pub kind: String,
    /// Metadata about the tree
    pub metadata: TreefileMetadata,
    /// Base image reference
    pub base_image: Option<String>,
    /// Package repositories
    pub repositories: Vec<Repository>,
    /// Package configuration
    pub packages: PackageConfig,
    /// Customizations to apply
    pub customizations: Option<Customizations>,
    /// Output configuration
    pub output: Option<OutputConfig>,
}

/// Treefile metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TreefileMetadata {
    /// Reference name for the tree
    pub ref_name: String,
    /// Version string
    pub version: Option<String>,
    /// Description
    pub description: Option<String>,
    /// Timestamp
    pub timestamp: Option<String>,
    /// Parent reference
    pub parent: Option<String>,
}

/// Package repository configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Repository {
    /// Repository name
    pub name: String,
    /// Repository URL
    pub url: String,
    /// Suite/distribution
    pub suite: String,
    /// Components
    pub components: Vec<String>,
    /// Whether enabled
    pub enabled: bool,
    /// GPG key
    pub gpg_key: Option<String>,
}

/// Package configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageConfig {
    /// Base packages
    pub base: Option<Vec<String>>,
    /// Additional packages
    pub additional: Option<Vec<String>>,
    /// Excluded packages
    pub excludes: Option<Vec<String>>,
}

/// Package override configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageOverride {
    /// Package name
    pub name: String,
    /// Override version
    pub version: Option<String>,
    /// Override architecture
    pub architecture: Option<String>,
    /// Override repository
    pub repository: Option<String>,
}

/// Customizations to apply
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Customizations {
    /// File modifications
    pub files: Option<Vec<FileModification>>,
    /// System modifications
    pub system: Option<Vec<SystemModification>>,
    /// Custom scripts
    pub scripts: Option<Vec<Script>>,
}

/// File modification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileModification {
    /// File path
    pub path: String,
    /// Content to write
    pub content: Option<String>,
    /// Source file to copy
    pub source: Option<String>,
    /// File permissions
    pub permissions: Option<u32>,
    /// Owner
    pub owner: Option<String>,
    /// Group
    pub group: Option<String>,
}

/// System modification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemModification {
    /// Modification type
    pub r#type: String,
    /// Parameters
    pub parameters: std::collections::HashMap<String, serde_json::Value>,
}

/// Custom script
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Script {
    /// Script name
    pub name: String,
    /// Script content
    pub content: String,
    /// Script interpreter
    pub interpreter: Option<String>,
    /// Whether to run as root
    pub run_as_root: Option<bool>,
    /// Script arguments
    pub arguments: Option<Vec<String>>,
}

/// Output configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputConfig {
    /// Whether to generate container images
    pub generate_container: bool,
    /// Container image path
    pub container_path: Option<String>,
    /// Export formats
    pub export_formats: Vec<String>,
}

impl Treefile {
    /// Parse a treefile from a file path
    pub async fn parse_treefile(path: &str) -> AptOstreeResult<Self> {
        let content = tokio::fs::read_to_string(path).await
            .map_err(|e| AptOstreeError::System(format!("Failed to read treefile {}: {}", path, e)))?;
        Self::parse_treefile_content(&content)
    }
    
    /// Parse treefile content from a string
    pub fn parse_treefile_content(content: &str) -> AptOstreeResult<Self> {
        // Try YAML first, then JSON
        if let Ok(treefile) = serde_yaml::from_str::<Treefile>(content) {
            return Ok(treefile);
        }
        
        if let Ok(treefile) = serde_json::from_str::<Treefile>(content) {
            return Ok(treefile);
        }
        
        Err(AptOstreeError::System("Failed to parse treefile content".to_string()))
    }
    
    /// Validate the treefile
    pub fn validate(&self) -> AptOstreeResult<()> {
        if self.api_version.is_empty() {
            return Err(AptOstreeError::System("API version cannot be empty".to_string()));
        }
        
        if self.kind.is_empty() {
            return Err(AptOstreeError::System("Kind cannot be empty".to_string()));
        }
        
        if self.metadata.ref_name.is_empty() {
            return Err(AptOstreeError::System("Reference name cannot be empty".to_string()));
        }
        
        if self.repositories.is_empty() {
            return Err(AptOstreeError::System("At least one repository must be specified".to_string()));
        }
        
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_parse_treefile_content() {
        let yaml_content = r#"
api_version: "1.0"
kind: "tree"
metadata:
  ref_name: "test/debian/trixie"
  version: "1.0.0"
  description: "Test tree"
repositories:
  - name: "debian"
    url: "http://deb.debian.org/debian"
    suite: "trixie"
    components: ["main", "contrib"]
    enabled: true
packages:
  base: ["systemd", "bash"]
  additional: ["curl"]
output:
  generate_container: true
  export_formats: ["docker-archive"]
"#;
        
        let treefile = Treefile::parse_treefile_content(yaml_content).unwrap();
        assert_eq!(treefile.api_version, "1.0");
        assert_eq!(treefile.kind, "tree");
        assert_eq!(treefile.metadata.ref_name, "test/debian/trixie");
        assert_eq!(treefile.repositories.len(), 1);
        assert_eq!(treefile.packages.base.as_ref().unwrap().len(), 2);
    }
    
    #[test]
    fn test_validate_treefile() {
        let mut treefile = Treefile {
            api_version: "1.0".to_string(),
            kind: "tree".to_string(),
            metadata: TreefileMetadata {
                ref_name: "test/debian/trixie".to_string(),
                version: None,
                description: None,
                timestamp: None,
                parent: None,
            },
            base_image: None,
            repositories: vec![
                Repository {
                    name: "debian".to_string(),
                    url: "http://deb.debian.org/debian".to_string(),
                    suite: "trixie".to_string(),
                    components: vec!["main".to_string()],
                    enabled: true,
                    gpg_key: None,
                }
            ],
            packages: PackageConfig {
                base: None,
                additional: None,
                excludes: None,
            },
            customizations: None,
            output: None,
        };
        
        assert!(treefile.validate().is_ok());
        
        treefile.metadata.ref_name = "".to_string();
        assert!(treefile.validate().is_err());
    }
}
