//! Treefile Processing for APT-OSTree
//! 
//! This module implements treefile parsing and processing for the compose system.
//! Treefiles are JSON/YAML configuration files that define how to compose an OSTree image.

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};

use crate::error::{AptOstreeError, AptOstreeResult};

/// Treefile configuration structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Treefile {
    /// Base image reference (e.g., "ubuntu:24.04")
    #[serde(default)]
    pub base: Option<String>,
    
    /// OSTree branch to use as base
    #[serde(default)]
    pub ostree_branch: Option<String>,
    
    /// Packages to install
    #[serde(default)]
    pub packages: Vec<String>,
    
    /// Packages to remove
    #[serde(default)]
    pub remove_packages: Vec<String>,
    
    /// Package overrides
    #[serde(default)]
    pub overrides: HashMap<String, String>,
    
    /// Repository configuration
    #[serde(default)]
    pub repos: Vec<RepoConfig>,
    
    /// Filesystem configuration
    #[serde(default)]
    pub filesystem: FilesystemConfig,
    
    /// Metadata configuration
    #[serde(default)]
    pub metadata: MetadataConfig,
    
    /// Postprocessing configuration
    #[serde(default)]
    pub postprocess: PostprocessConfig,
    
    /// Container configuration
    #[serde(default)]
    pub container: ContainerConfig,
}

/// Repository configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepoConfig {
    /// Repository name
    pub name: String,
    
    /// Repository URL
    pub url: String,
    
    /// Repository type (deb, deb-src)
    #[serde(default = "default_repo_type")]
    pub r#type: String,
    
    /// Repository components
    #[serde(default)]
    pub components: Vec<String>,
    
    /// GPG key
    #[serde(default)]
    pub gpg_key: Option<String>,
    
    /// Enabled flag
    #[serde(default = "default_enabled")]
    pub enabled: bool,
}

/// Filesystem configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FilesystemConfig {
    /// Root filesystem path
    #[serde(default = "default_rootfs")]
    pub rootfs: String,
    
    /// Staging directory
    #[serde(default = "default_staging")]
    pub staging: String,
    
    /// Cache directory
    #[serde(default = "default_cache")]
    pub cache: String,
    
    /// Preserve permissions
    #[serde(default = "default_preserve_permissions")]
    pub preserve_permissions: bool,
    
    /// Preserve timestamps
    #[serde(default = "default_preserve_timestamps")]
    pub preserve_timestamps: bool,
    
    /// Enable hardlinks
    #[serde(default = "default_enable_hardlinks")]
    pub enable_hardlinks: bool,
}

/// Metadata configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MetadataConfig {
    /// Commit subject
    #[serde(default = "default_commit_subject")]
    pub commit_subject: String,
    
    /// Commit body
    #[serde(default)]
    pub commit_body: Option<String>,
    
    /// Author
    #[serde(default = "default_author")]
    pub author: String,
    
    /// Version
    #[serde(default)]
    pub version: Option<String>,
    
    /// Labels
    #[serde(default)]
    pub labels: HashMap<String, String>,
}

/// Postprocessing configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PostprocessConfig {
    /// Enable postprocessing
    #[serde(default = "default_postprocess_enabled")]
    pub enabled: bool,
    
    /// Scripts to run
    #[serde(default)]
    pub scripts: Vec<String>,
    
    /// Environment variables
    #[serde(default)]
    pub environment: HashMap<String, String>,
}

/// Container configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ContainerConfig {
    /// Container name
    #[serde(default)]
    pub name: Option<String>,
    
    /// Container tag
    #[serde(default = "default_container_tag")]
    pub tag: String,
    
    /// Architecture
    #[serde(default = "default_architecture")]
    pub architecture: String,
    
    /// OS
    #[serde(default = "default_os")]
    pub os: String,
    
    /// Entrypoint
    #[serde(default)]
    pub entrypoint: Option<Vec<String>>,
    
    /// Command
    #[serde(default)]
    pub cmd: Option<Vec<String>>,
    
    /// Environment variables
    #[serde(default)]
    pub env: Vec<String>,
    
    /// Working directory
    #[serde(default)]
    pub working_dir: Option<String>,
    
    /// User
    #[serde(default)]
    pub user: Option<String>,
    
    /// Labels
    #[serde(default)]
    pub labels: HashMap<String, String>,
}

/// Treefile processor
pub struct TreefileProcessor {
    treefile: Treefile,
    work_dir: PathBuf,
}

/// Processing options
#[derive(Debug, Clone)]
pub struct ProcessingOptions {
    pub dry_run: bool,
    pub print_only: bool,
    pub force_nocache: bool,
    pub cachedir: Option<String>,
    pub repo: Option<String>,
}

/// Processing result
#[derive(Debug, Clone)]
pub struct ProcessingResult {
    pub success: bool,
    pub commit_id: Option<String>,
    pub packages_installed: Vec<String>,
    pub packages_removed: Vec<String>,
    pub error_message: Option<String>,
}

// Default value functions
fn default_repo_type() -> String { "deb".to_string() }
fn default_enabled() -> bool { true }
fn default_rootfs() -> String { "/var/lib/apt-ostree/rootfs".to_string() }
fn default_staging() -> String { "/var/lib/apt-ostree/staging".to_string() }
fn default_cache() -> String { "/var/lib/apt-ostree/cache".to_string() }
fn default_preserve_permissions() -> bool { true }
fn default_preserve_timestamps() -> bool { true }
fn default_enable_hardlinks() -> bool { true }
fn default_commit_subject() -> String { "apt-ostree compose".to_string() }
fn default_author() -> String { "apt-ostree <apt-ostree@example.com>".to_string() }
fn default_postprocess_enabled() -> bool { true }
fn default_container_tag() -> String { "latest".to_string() }
fn default_architecture() -> String { "amd64".to_string() }
fn default_os() -> String { "linux".to_string() }

impl Treefile {
    /// Load treefile from path
    pub async fn from_path<P: AsRef<Path>>(path: P) -> AptOstreeResult<Self> {
        let path = path.as_ref();
        info!("Loading treefile from: {}", path.display());
        
        let content = fs::read_to_string(path)
            .map_err(|e| AptOstreeError::Io(e))?;
        
        // Try to parse as JSON first, then YAML
        if let Ok(treefile) = serde_json::from_str(&content) {
            info!("Successfully parsed treefile as JSON");
            Ok(treefile)
        } else if let Ok(treefile) = serde_yaml::from_str(&content) {
            info!("Successfully parsed treefile as YAML");
            Ok(treefile)
        } else {
            Err(AptOstreeError::InvalidArgument(
                "Failed to parse treefile as JSON or YAML".to_string()
            ))
        }
    }
    
    /// Validate treefile configuration
    pub fn validate(&self) -> AptOstreeResult<()> {
        info!("Validating treefile configuration");
        
        // Check that we have either base or ostree_branch
        if self.base.is_none() && self.ostree_branch.is_none() {
            return Err(AptOstreeError::InvalidArgument(
                "Either 'base' or 'ostree_branch' must be specified".to_string()
            ));
        }
        
        // Validate repository configurations
        for repo in &self.repos {
            if repo.name.is_empty() {
                return Err(AptOstreeError::InvalidArgument(
                    "Repository name cannot be empty".to_string()
                ));
            }
            if repo.url.is_empty() {
                return Err(AptOstreeError::InvalidArgument(
                    format!("Repository URL cannot be empty for repo: {}", repo.name)
                ));
            }
        }
        
        info!("Treefile validation successful");
        Ok(())
    }
    
    /// Get effective base branch
    pub fn get_base_branch(&self) -> AptOstreeResult<String> {
        if let Some(ref branch) = self.ostree_branch {
            Ok(branch.clone())
        } else if let Some(ref base) = self.base {
            // Convert base image reference to branch
            let parts: Vec<&str> = base.split(':').collect();
            match parts.as_slice() {
                [distribution, version] => {
                    Ok(format!("{}/{}/x86_64", distribution, version))
                },
                _ => {
                    Err(AptOstreeError::InvalidArgument(
                        format!("Invalid base image format: {}", base)
                    ))
                }
            }
        } else {
            Err(AptOstreeError::InvalidArgument(
                "No base image or branch specified".to_string()
            ))
        }
    }
}

impl TreefileProcessor {
    /// Create new treefile processor
    pub fn new(treefile: Treefile, work_dir: PathBuf) -> Self {
        Self { treefile, work_dir }
    }
    
    /// Process treefile
    pub async fn process(&self, options: &ProcessingOptions) -> AptOstreeResult<ProcessingResult> {
        info!("Processing treefile with options: {:?}", options);
        
        // Validate treefile
        self.treefile.validate()?;
        
        if options.print_only {
            return self.print_expanded_treefile().await;
        }
        
        if options.dry_run {
            return self.dry_run_process().await;
        }
        
        // Full processing
        self.full_process(options).await
    }
    
    /// Print expanded treefile
    async fn print_expanded_treefile(&self) -> AptOstreeResult<ProcessingResult> {
        info!("Printing expanded treefile");
        
        let expanded = serde_json::to_string_pretty(&self.treefile)
            .map_err(|e| AptOstreeError::Json(e))?;
        
        println!("{}", expanded);
        
        Ok(ProcessingResult {
            success: true,
            commit_id: None,
            packages_installed: vec![],
            packages_removed: vec![],
            error_message: None,
        })
    }
    
    /// Dry run processing
    async fn dry_run_process(&self) -> AptOstreeResult<ProcessingResult> {
        info!("Performing dry run processing");
        
        let base_branch = self.treefile.get_base_branch()?;
        println!("Base branch: {}", base_branch);
        
        if !self.treefile.packages.is_empty() {
            println!("Packages to install:");
            for pkg in &self.treefile.packages {
                println!("  + {}", pkg);
            }
        }
        
        if !self.treefile.remove_packages.is_empty() {
            println!("Packages to remove:");
            for pkg in &self.treefile.remove_packages {
                println!("  - {}", pkg);
            }
        }
        
        if !self.treefile.repos.is_empty() {
            println!("Repositories:");
            for repo in &self.treefile.repos {
                println!("  {}: {}", repo.name, repo.url);
            }
        }
        
        Ok(ProcessingResult {
            success: true,
            commit_id: None,
            packages_installed: self.treefile.packages.clone(),
            packages_removed: self.treefile.remove_packages.clone(),
            error_message: None,
        })
    }
    
    /// Full processing
    async fn full_process(&self, _options: &ProcessingOptions) -> AptOstreeResult<ProcessingResult> {
        info!("Performing full processing");
        
        // TODO: Implement full processing
        // 1. Setup repositories
        // 2. Download and install packages
        // 3. Create OSTree commit
        // 4. Apply postprocessing
        
        warn!("Full processing not yet implemented");
        
        Ok(ProcessingResult {
            success: false,
            commit_id: None,
            packages_installed: vec![],
            packages_removed: vec![],
            error_message: Some("Full processing not yet implemented".to_string()),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;
    
    #[tokio::test]
    async fn test_treefile_parsing() {
        let json_content = r#"{
            "base": "ubuntu:24.04",
            "packages": ["vim", "git"],
            "repos": [
                {
                    "name": "main",
                    "url": "http://archive.ubuntu.com/ubuntu",
                    "components": ["main", "universe"]
                }
            ]
        }"#;
        
        let temp_dir = tempdir().unwrap();
        let treefile_path = temp_dir.path().join("test.treefile");
        tokio::fs::write(&treefile_path, json_content).await.unwrap();
        
        let treefile = Treefile::from_path(&treefile_path).await.unwrap();
        assert_eq!(treefile.base, Some("ubuntu:24.04".to_string()));
        assert_eq!(treefile.packages, vec!["vim", "git"]);
        assert_eq!(treefile.repos.len(), 1);
        assert_eq!(treefile.repos[0].name, "main");
    }
    
    #[tokio::test]
    async fn test_treefile_validation() {
        let mut treefile = Treefile {
            base: Some("ubuntu:24.04".to_string()),
            ostree_branch: None,
            packages: vec![],
            remove_packages: vec![],
            overrides: HashMap::new(),
            repos: vec![],
            filesystem: FilesystemConfig {
                rootfs: "/tmp/rootfs".to_string(),
                staging: "/tmp/staging".to_string(),
                cache: "/tmp/cache".to_string(),
                preserve_permissions: true,
                preserve_timestamps: true,
                enable_hardlinks: true,
            },
            metadata: MetadataConfig {
                commit_subject: "test".to_string(),
                commit_body: None,
                author: "test".to_string(),
                version: None,
                labels: HashMap::new(),
            },
            postprocess: PostprocessConfig {
                enabled: true,
                scripts: vec![],
                environment: HashMap::new(),
            },
            container: ContainerConfig {
                name: None,
                tag: "latest".to_string(),
                architecture: "amd64".to_string(),
                os: "linux".to_string(),
                entrypoint: None,
                cmd: None,
                env: vec![],
                working_dir: None,
                user: None,
                labels: HashMap::new(),
            },
        };
        
        assert!(treefile.validate().is_ok());
        
        // Test invalid treefile
        treefile.base = None;
        treefile.ostree_branch = None;
        assert!(treefile.validate().is_err());
    }
} 