//! OSTree Commit Management for APT-OSTree
//! 
//! This module implements OSTree commit management for package layering,
//! providing atomic operations, rollback support, and commit history tracking.

use std::path::{Path, PathBuf};
use tracing::{info, warn, debug};
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};

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

/// OSTree commit metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OstreeCommitMetadata {
    pub commit_id: String,
    pub parent_commit: Option<String>,
    pub timestamp: DateTime<Utc>,
    pub subject: String,
    pub body: String,
    pub author: String,
    pub packages_added: Vec<String>,
    pub packages_removed: Vec<String>,
    pub packages_modified: Vec<String>,
    pub layer_level: usize,
    pub deployment_type: DeploymentType,
    pub checksum: String,
}

/// Deployment type
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeploymentType {
    Base,
    PackageLayer,
    SystemUpdate,
    Rollback,
    Custom,
}

/// OSTree commit manager
pub struct OstreeCommitManager {
    repo_path: PathBuf,
    branch_name: String,
    current_commit: Option<String>,
    commit_history: Vec<OstreeCommitMetadata>,
    layer_counter: usize,
}

/// Commit creation options
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitOptions {
    pub subject: String,
    pub body: Option<String>,
    pub author: Option<String>,
    pub layer_level: Option<usize>,
    pub deployment_type: DeploymentType,
    pub dry_run: bool,
}

/// Commit result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitResult {
    pub success: bool,
    pub commit_id: Option<String>,
    pub parent_commit: Option<String>,
    pub metadata: Option<OstreeCommitMetadata>,
    pub error_message: Option<String>,
}

impl Default for CommitOptions {
    fn default() -> Self {
        Self {
            subject: "Package layer update".to_string(),
            body: None,
            author: Some("apt-ostree <apt-ostree@example.com>".to_string()),
            layer_level: None,
            deployment_type: DeploymentType::PackageLayer,
            dry_run: false,
        }
    }
}

impl OstreeCommitManager {
    /// Create a new OSTree commit manager
    pub fn new(repo_path: PathBuf, branch_name: String) -> AptOstreeResult<Self> {
        info!("Creating OSTree commit manager for branch: {} at {}", branch_name, repo_path.display());
        
        // Ensure repository exists
        if !repo_path.exists() {
            return Err(AptOstreeError::Ostree(
                format!("OSTree repository not found: {}", repo_path.display())
            ));
        }
        
        Ok(Self {
            repo_path,
            branch_name,
            current_commit: None,
            commit_history: Vec::new(),
            layer_counter: 0,
        })
    }
    
    /// Initialize commit manager
    pub async fn initialize(&mut self) -> AptOstreeResult<()> {
        info!("Initializing OSTree commit manager");
        
        // Get current commit
        self.current_commit = self.get_current_commit().await?;
        
        // Load commit history
        self.load_commit_history().await?;
        
        // Initialize layer counter
        self.layer_counter = self.get_next_layer_level();
        
        info!("OSTree commit manager initialized. Current commit: {:?}, Layer counter: {}", 
              self.current_commit, self.layer_counter);
        Ok(())
    }
    
    /// Get current commit
    pub async fn get_current_commit(&self) -> AptOstreeResult<Option<String>> {
        let output = std::process::Command::new("ostree")
            .args(&["rev-parse", &self.branch_name])
            .current_dir(&self.repo_path)
            .output();
        
        match output {
            Ok(output) => {
                if output.status.success() {
                    let commit_id = String::from_utf8_lossy(&output.stdout).trim().to_string();
                    Ok(Some(commit_id))
                } else {
                    warn!("No current commit found for branch: {}", self.branch_name);
                    Ok(None)
                }
            }
            Err(e) => {
                warn!("Failed to get current commit: {}", e);
                Ok(None)
            }
        }
    }
    
    /// Load commit history
    async fn load_commit_history(&mut self) -> AptOstreeResult<()> {
        debug!("Loading commit history");
        
        if let Some(current_commit) = &self.current_commit {
            let output = std::process::Command::new("ostree")
                .args(&["log", current_commit])
                .current_dir(&self.repo_path)
                .output();
            
            if let Ok(output) = output {
                if output.status.success() {
                    self.parse_commit_log(&output.stdout)?;
                }
            }
        }
        
        info!("Loaded {} commits from history", self.commit_history.len());
        Ok(())
    }
    
    /// Parse commit log
    fn parse_commit_log(&mut self, log_output: &[u8]) -> AptOstreeResult<()> {
        let log_text = String::from_utf8_lossy(log_output);
        let lines: Vec<&str> = log_text.lines().collect();
        
        let mut current_commit: Option<OstreeCommitMetadata> = None;
        
        for line in lines {
            if line.starts_with("commit ") {
                // Save previous commit if exists
                if let Some(commit) = current_commit.take() {
                    self.commit_history.push(commit);
                }
                
                // Start new commit
                let commit_id = line[7..].trim();
                current_commit = Some(OstreeCommitMetadata {
                    commit_id: commit_id.to_string(),
                    parent_commit: None,
                    timestamp: Utc::now(),
                    subject: String::new(),
                    body: String::new(),
                    author: String::new(),
                    packages_added: Vec::new(),
                    packages_removed: Vec::new(),
                    packages_modified: Vec::new(),
                    layer_level: 0,
                    deployment_type: DeploymentType::Custom,
                    checksum: String::new(),
                });
            } else if let Some(ref mut commit) = current_commit {
                if line.starts_with("Subject: ") {
                    commit.subject = line[9..].trim().to_string();
                } else if line.starts_with("Author: ") {
                    commit.author = line[8..].trim().to_string();
                } else if line.starts_with("Date: ") {
                    // Parse date if needed
                } else if !line.is_empty() && !line.starts_with(" ") {
                    // Body content
                    commit.body.push_str(line);
                    commit.body.push('\n');
                }
            }
        }
        
        // Save last commit
        if let Some(commit) = current_commit {
            self.commit_history.push(commit);
        }
        
        Ok(())
    }
    
    /// Create a new commit with package changes
    pub async fn create_package_commit(
        &mut self,
        packages_added: &[DebPackageMetadata],
        packages_removed: &[String],
        options: CommitOptions,
    ) -> AptOstreeResult<CommitResult> {
        info!("Creating package commit with {} added, {} removed packages", 
              packages_added.len(), packages_removed.len());
        
        if options.dry_run {
            info!("DRY RUN: Would create commit with subject: {}", options.subject);
            return Ok(CommitResult {
                success: true,
                commit_id: None,
                parent_commit: self.current_commit.clone(),
                metadata: None,
                error_message: Some("Dry run mode".to_string()),
            });
        }
        
        // Prepare commit metadata
        let layer_level = options.layer_level.unwrap_or_else(|| {
            self.layer_counter += 1;
            self.layer_counter
        });
        
        let packages_added_names: Vec<String> = packages_added.iter()
            .map(|pkg| pkg.name.clone())
            .collect();
        
        let metadata = OstreeCommitMetadata {
            commit_id: String::new(), // Will be set after commit
            parent_commit: self.current_commit.clone(),
            timestamp: Utc::now(),
            subject: options.subject,
            body: options.body.unwrap_or_default(),
            author: options.author.unwrap_or_else(|| "apt-ostree <apt-ostree@example.com>".to_string()),
            packages_added: packages_added_names,
            packages_removed: packages_removed.to_vec(),
            packages_modified: Vec::new(),
            layer_level,
            deployment_type: options.deployment_type,
            checksum: String::new(),
        };
        
        // Create OSTree commit
        let commit_id = self.create_ostree_commit(&metadata).await?;
        
        // Update metadata with commit ID
        let mut final_metadata = metadata.clone();
        final_metadata.commit_id = commit_id.clone();
        
        // Add to history
        self.commit_history.push(final_metadata.clone());
        
        // Update current commit
        self.current_commit = Some(commit_id.clone());
        
        info!("Created package commit: {} (layer: {})", commit_id, layer_level);
        
        Ok(CommitResult {
            success: true,
            commit_id: Some(commit_id),
            parent_commit: metadata.parent_commit,
            metadata: Some(final_metadata),
            error_message: None,
        })
    }
    
    /// Create OSTree commit
    pub async fn create_ostree_commit(&self, metadata: &OstreeCommitMetadata) -> AptOstreeResult<String> {
        debug!("Creating OSTree commit with subject: {}", metadata.subject);
        
        // Prepare commit message
        let commit_message = self.format_commit_message(metadata);
        
        // Create temporary commit message file
        let temp_dir = std::env::temp_dir();
        let message_file = temp_dir.join(format!("apt-ostree-commit-{}.msg", chrono::Utc::now().timestamp()));
        std::fs::write(&message_file, commit_message)?;
        
        // Build ostree commit command
        let mut cmd = std::process::Command::new("/usr/bin/ostree");
        cmd.args(&["commit", "--branch", &self.branch_name]);
        
        if let Some(parent) = &metadata.parent_commit {
            cmd.args(&["--parent", parent]);
        }
        
        cmd.args(&["--body-file", message_file.to_str().unwrap()]);
        cmd.current_dir(&self.repo_path);
        
        // Execute commit
        let output = cmd.output()
            .map_err(|e| AptOstreeError::Ostree(format!("Failed to create OSTree commit: {}", e)))?;
        
        // Clean up message file
        let _ = std::fs::remove_file(&message_file);
        
        if !output.status.success() {
            let error_msg = String::from_utf8_lossy(&output.stderr);
            return Err(AptOstreeError::Ostree(
                format!("OSTree commit failed: {}", error_msg)
            ));
        }
        
        // Get commit ID from output
        let commit_id = String::from_utf8_lossy(&output.stdout).trim().to_string();
        
        Ok(commit_id)
    }
    
    /// Format commit message
    fn format_commit_message(&self, metadata: &OstreeCommitMetadata) -> String {
        let mut message = format!("{}\n\n", metadata.subject);
        
        if !metadata.body.is_empty() {
            message.push_str(&metadata.body);
            message.push_str("\n\n");
        }
        
        message.push_str("Package Changes:\n");
        
        if !metadata.packages_added.is_empty() {
            message.push_str("Added:\n");
            for package in &metadata.packages_added {
                message.push_str(&format!("  + {}\n", package));
            }
            message.push('\n');
        }
        
        if !metadata.packages_removed.is_empty() {
            message.push_str("Removed:\n");
            for package in &metadata.packages_removed {
                message.push_str(&format!("  - {}\n", package));
            }
            message.push('\n');
        }
        
        if !metadata.packages_modified.is_empty() {
            message.push_str("Modified:\n");
            for package in &metadata.packages_modified {
                message.push_str(&format!("  ~ {}\n", package));
            }
            message.push('\n');
        }
        
        message.push_str(&format!("Layer Level: {}\n", metadata.layer_level));
        message.push_str(&format!("Deployment Type: {:?}\n", metadata.deployment_type));
        message.push_str(&format!("Timestamp: {}\n", metadata.timestamp));
        message.push_str(&format!("Author: {}\n", metadata.author));
        
        message
    }
    
    /// Rollback to previous commit
    pub async fn rollback_to_commit(&mut self, commit_id: &str) -> AptOstreeResult<CommitResult> {
        info!("Rolling back to commit: {}", commit_id);
        
        // Verify commit exists
        if !self.commit_exists(commit_id).await? {
            return Err(AptOstreeError::Ostree(
                format!("Commit not found: {}", commit_id)
            ));
        }
        
        // Create rollback commit
        let options = CommitOptions {
            subject: format!("Rollback to commit {}", commit_id),
            body: Some(format!("Rolling back from {} to {}", 
                             self.current_commit.as_deref().unwrap_or("none"), commit_id)),
            author: Some("apt-ostree <apt-ostree@example.com>".to_string()),
            layer_level: Some(self.layer_counter + 1),
            deployment_type: DeploymentType::Rollback,
            dry_run: false,
        };
        
        let rollback_metadata = OstreeCommitMetadata {
            commit_id: String::new(),
            parent_commit: self.current_commit.clone(),
            timestamp: Utc::now(),
            subject: options.subject.clone(),
            body: options.body.clone().unwrap_or_default(),
            author: options.author.clone().unwrap_or_default(),
            packages_added: Vec::new(),
            packages_removed: Vec::new(),
            packages_modified: Vec::new(),
            layer_level: options.layer_level.unwrap_or(0),
            deployment_type: DeploymentType::Rollback,
            checksum: String::new(),
        };
        
        // Create rollback commit
        let new_commit_id = self.create_ostree_commit(&rollback_metadata).await?;
        
        // Update current commit
        self.current_commit = Some(new_commit_id.clone());
        
        // Add to history
        let parent_commit = rollback_metadata.parent_commit.clone();
        let mut final_metadata = rollback_metadata;
        final_metadata.commit_id = new_commit_id.clone();
        self.commit_history.push(final_metadata.clone());
        
        info!("Rollback completed to commit: {}", new_commit_id);
        
        Ok(CommitResult {
            success: true,
            commit_id: Some(new_commit_id),
            parent_commit,
            metadata: Some(final_metadata),
            error_message: None,
        })
    }
    
    /// Check if commit exists
    async fn commit_exists(&self, commit_id: &str) -> AptOstreeResult<bool> {
        let output = std::process::Command::new("/usr/bin/ostree")
            .args(&["show", commit_id])
            .current_dir(&self.repo_path)
            .output();
        
        match output {
            Ok(output) => Ok(output.status.success()),
            Err(_) => Ok(false),
        }
    }
    
    /// Get commit history
    pub fn get_commit_history(&self) -> &[OstreeCommitMetadata] {
        &self.commit_history
    }
    
    /// Get next layer level
    fn get_next_layer_level(&self) -> usize {
        self.commit_history.iter()
            .map(|commit| commit.layer_level)
            .max()
            .unwrap_or(0) + 1
    }
    
    /// Get commits by layer level
    pub fn get_commits_by_layer(&self, layer_level: usize) -> Vec<&OstreeCommitMetadata> {
        self.commit_history.iter()
            .filter(|commit| commit.layer_level == layer_level)
            .collect()
    }
    
    /// Get commits by deployment type
    pub fn get_commits_by_type(&self, deployment_type: &DeploymentType) -> Vec<&OstreeCommitMetadata> {
        self.commit_history.iter()
            .filter(|commit| std::mem::discriminant(&commit.deployment_type) == std::mem::discriminant(deployment_type))
            .collect()
    }
    
    /// Get commit metadata
    pub fn get_commit_metadata(&self, commit_id: &str) -> Option<&OstreeCommitMetadata> {
        self.commit_history.iter()
            .find(|commit| commit.commit_id == commit_id)
    }
    
    /// Get repository path
    pub fn get_repo_path(&self) -> &Path {
        &self.repo_path
    }
    
    /// Get branch name
    pub fn get_branch_name(&self) -> &str {
        &self.branch_name
    }
    
    /// Get layer counter
    pub fn get_layer_counter(&self) -> usize {
        self.layer_counter
    }
} 