//! OSTree operations for apt-ostree daemon

use crate::daemon::{DaemonResult, DaemonError};
use std::process::Command;
use std::path::Path;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};

/// Deployment information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentInfo {
    pub id: String,
    pub commit: String,
    pub refspec: String,
    pub is_current: bool,
    pub is_pinned: bool,
    pub created_at: String,
    pub size: u64,
}

/// OSTree manager for the daemon
pub struct OstreeManager {
    sysroot_path: String,
}

impl OstreeManager {
    pub fn new(sysroot_path: &str) -> DaemonResult<Self> {
        // Verify sysroot path exists
        let sysroot = Path::new(sysroot_path);
        if !sysroot.exists() {
            return Err(DaemonError::System(format!("Sysroot path does not exist: {}", sysroot_path)));
        }

        // Check if OSTree is available
        if !Self::is_ostree_available()? {
            return Err(DaemonError::System("OSTree is not available on this system".to_string()));
        }

        Ok(Self {
            sysroot_path: sysroot_path.to_string(),
        })
    }

    pub fn get_deployments(&self) -> DaemonResult<Vec<DeploymentInfo>> {
        tracing::info!("Getting OSTree deployments");
        
        // Run ostree admin status to get deployment information
        let output = Command::new("ostree")
            .arg("admin")
            .arg("status")
            .arg("--sysroot")
            .arg(&self.sysroot_path)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree admin status: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree admin status failed: {}", error)));
        }

        let output_str = String::from_utf8_lossy(&output.stdout);
        let deployments = self.parse_deployment_status(&output_str)?;
        
        Ok(deployments)
    }

    pub fn get_current_deployment(&self) -> DaemonResult<String> {
        let deployments = self.get_deployments()?;
        
        for deployment in deployments {
            if deployment.is_current {
                return Ok(deployment.id);
            }
        }
        
        Err(DaemonError::System("No current deployment found".to_string()))
    }

    /// Get system status
    pub fn get_system_status(&self) -> DaemonResult<String> {
        let deployments = self.get_deployments()?;
        let current_deployment = self.get_current_deployment().ok();
        
        let mut status = HashMap::new();
        status.insert("deployment_count", deployments.len().to_string());
        status.insert("current_deployment", current_deployment.unwrap_or_else(|| "none".to_string()));
        status.insert("ostree_available", "true".to_string());
        
        // Convert to JSON
        serde_json::to_string(&status)
            .map_err(|e| DaemonError::System(format!("Failed to serialize status: {}", e)))
    }

    /// Rollback deployment
    pub fn rollback_deployment(&self) -> DaemonResult<()> {
        tracing::info!("Rolling back deployment");
        
        // Run ostree admin rollback
        let output = Command::new("ostree")
            .arg("admin")
            .arg("rollback")
            .arg("--sysroot")
            .arg(&self.sysroot_path)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree admin rollback: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree admin rollback failed: {}", error)));
        }

        tracing::info!("Deployment rolled back successfully");
        Ok(())
    }

    /// Rebase to refspec
    pub fn rebase_to_ref(&self, refspec: &str) -> DaemonResult<()> {
        tracing::info!("Rebasing to refspec: {}", refspec);
        
        // Run ostree admin rebase
        let output = Command::new("ostree")
            .arg("admin")
            .arg("rebase")
            .arg("--sysroot")
            .arg(&self.sysroot_path)
            .arg(refspec)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree admin rebase: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree admin rebase failed: {}", error)));
        }

        tracing::info!("Successfully rebased to refspec: {}", refspec);
        Ok(())
    }

    pub fn deploy_ref(&self, refspec: &str) -> DaemonResult<()> {
        tracing::info!("Deploying refspec: {}", refspec);
        
        // Run ostree admin deploy
        let output = Command::new("ostree")
            .arg("admin")
            .arg("deploy")
            .arg("--sysroot")
            .arg(&self.sysroot_path)
            .arg(refspec)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree admin deploy: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree admin deploy failed: {}", error)));
        }

        tracing::info!("Successfully deployed refspec: {}", refspec);
        Ok(())
    }

    pub fn rollback(&self) -> DaemonResult<()> {
        tracing::info!("Rolling back deployment");
        
        // Run ostree admin rollback
        let output = Command::new("ostree")
            .arg("admin")
            .arg("rollback")
            .arg("--sysroot")
            .arg(&self.sysroot_path)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree admin rollback: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree admin rollback failed: {}", error)));
        }

        tracing::info!("Successfully rolled back deployment");
        Ok(())
    }

    pub fn rebase(&self, refspec: &str) -> DaemonResult<()> {
        tracing::info!("Rebasing to refspec: {}", refspec);
        
        // Run ostree admin rebase
        let output = Command::new("ostree")
            .arg("admin")
            .arg("rebase")
            .arg("--sysroot")
            .arg(&self.sysroot_path)
            .arg(refspec)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree admin rebase: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree admin rebase failed: {}", error)));
        }

        tracing::info!("Successfully rebased to refspec: {}", refspec);
        Ok(())
    }

    pub fn get_repo_info(&self) -> DaemonResult<HashMap<String, String>> {
        tracing::info!("Getting OSTree repository information");
        
        // Run ostree refs to get repository information
        let output = Command::new("ostree")
            .arg("refs")
            .arg("--repo")
            .arg(&self.sysroot_path)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree refs: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree refs failed: {}", error)));
        }

        let output_str = String::from_utf8_lossy(&output.stdout);
        let refs = self.parse_refs_output(&output_str)?;
        
        Ok(refs)
    }

    pub fn create_commit(&self, parent: &str, subject: &str, body: Option<&str>) -> DaemonResult<String> {
        tracing::info!("Creating OSTree commit with parent: {}", parent);
        
        // Create a temporary directory for the commit
        let temp_dir = tempfile::tempdir()
            .map_err(|e| DaemonError::System(format!("Failed to create temp directory: {}", e)))?;
        
        // Run ostree commit
        let mut cmd = Command::new("ostree");
        cmd.arg("commit");
        cmd.arg("--repo").arg(&self.sysroot_path);
        cmd.arg("--parent").arg(parent);
        cmd.arg("--subject").arg(subject);
        
        if let Some(body_text) = body {
            cmd.arg("--body").arg(body_text);
        }
        
        cmd.arg(temp_dir.path());
        
        let output = cmd.output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree commit: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree commit failed: {}", error)));
        }

        let output_str = String::from_utf8_lossy(&output.stdout);
        let commit_hash = self.extract_commit_hash(&output_str)?;
        
        tracing::info!("Successfully created commit: {}", commit_hash);
        Ok(commit_hash)
    }

    pub fn checkout_commit(&self, commit: &str, path: &str) -> DaemonResult<()> {
        tracing::info!("Checking out commit {} to {}", commit, path);
        
        // Run ostree checkout
        let output = Command::new("ostree")
            .arg("checkout")
            .arg("--repo")
            .arg(&self.sysroot_path)
            .arg(commit)
            .arg(path)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree checkout: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree checkout failed: {}", error)));
        }

        tracing::info!("Successfully checked out commit {} to {}", commit, path);
        Ok(())
    }

    pub fn get_commit_info(&self, commit: &str) -> DaemonResult<HashMap<String, String>> {
        tracing::info!("Getting commit information for: {}", commit);
        
        // Run ostree log to get commit information
        let output = Command::new("ostree")
            .arg("log")
            .arg("--repo")
            .arg(&self.sysroot_path)
            .arg(commit)
            .output()
            .map_err(|e| DaemonError::System(format!("Failed to run ostree log: {}", e)))?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(DaemonError::System(format!("ostree log failed: {}", error)));
        }

        let output_str = String::from_utf8_lossy(&output.stdout);
        let commit_info = self.parse_commit_log(&output_str)?;
        
        Ok(commit_info)
    }

    // Helper methods

    fn is_ostree_available() -> DaemonResult<bool> {
        let output = Command::new("ostree")
            .arg("--version")
            .output();
        
        Ok(output.is_ok())
    }

    fn parse_deployment_status(&self, output: &str) -> DaemonResult<Vec<DeploymentInfo>> {
        let mut deployments = Vec::new();
        let mut current_deployment = None;
        
        for line in output.lines() {
            if line.contains("*") {
                // This is the current deployment
                current_deployment = Some(line);
            } else if line.trim().starts_with("deployment") {
                // This is a deployment line
                if let Some(deployment) = self.parse_deployment_line(line) {
                    deployments.push(deployment);
                }
            }
        }
        
        // Mark the current deployment
        if let Some(current_line) = current_deployment {
            if let Some(deployment) = self.parse_deployment_line(current_line) {
                for dep in &mut deployments {
                    if dep.id == deployment.id {
                        dep.is_current = true;
                        break;
                    }
                }
            }
        }
        
        Ok(deployments)
    }

    fn parse_deployment_line(&self, line: &str) -> Option<DeploymentInfo> {
        // Parse lines like: "deployment1.0 (commit: abc123...)"
        let parts: Vec<&str> = line.split_whitespace().collect();
        if parts.len() >= 3 {
            let id = parts[0].trim_start_matches('*').to_string();
            let commit = parts[2].trim_matches('(').trim_matches(')').to_string();
            
            Some(DeploymentInfo {
                id,
                commit,
                refspec: String::new(), // Not available in status output
                is_current: line.contains('*'),
                is_pinned: false, // Not available in status output
                created_at: String::new(), // Not available in status output
                size: 0, // Not available in status output
            })
        } else {
            None
        }
    }

    fn parse_refs_output(&self, output: &str) -> DaemonResult<HashMap<String, String>> {
        let mut refs = HashMap::new();
        
        for line in output.lines() {
            if let Some((ref_name, commit)) = line.split_once(' ') {
                refs.insert(ref_name.to_string(), commit.to_string());
            }
        }
        
        Ok(refs)
    }

    fn extract_commit_hash(&self, output: &str) -> DaemonResult<String> {
        // Look for commit hash in output
        for line in output.lines() {
            if line.contains("commit") {
                let parts: Vec<&str> = line.split_whitespace().collect();
                for part in parts {
                    if part.len() == 64 && part.chars().all(|c| c.is_ascii_hexdigit()) {
                        return Ok(part.to_string());
                    }
                }
            }
        }
        
        Err(DaemonError::System("Could not extract commit hash from output".to_string()))
    }

    fn parse_commit_log(&self, output: &str) -> DaemonResult<HashMap<String, String>> {
        let mut commit_info = HashMap::new();
        
        for line in output.lines() {
            if let Some(hash) = line.strip_prefix("commit ") {
                commit_info.insert("hash".to_string(), hash.trim().to_string());
            } else if let Some(author) = line.strip_prefix("Author: ") {
                commit_info.insert("author".to_string(), author.trim().to_string());
            } else if let Some(date) = line.strip_prefix("Date: ") {
                commit_info.insert("date".to_string(), date.trim().to_string());
            } else if let Some(subject) = line.strip_prefix("Subject: ") {
                commit_info.insert("subject".to_string(), subject.trim().to_string());
            }
        }
        
        Ok(commit_info)
    }
}
