use zbus::{Connection, Proxy};
use std::error::Error;

/// Daemon client for communicating with apt-ostreed
pub struct DaemonClient {
    connection: Connection,
    proxy: Proxy<'static>,
}

impl DaemonClient {
    /// Create a new daemon client
    pub async fn new() -> Result<Self, Box<dyn Error>> {
        let connection = Connection::system().await?;
        let proxy = Proxy::new(
            &connection,
            "org.aptostree.dev",
            "/org/aptostree/dev/Daemon",
            "org.aptostree.dev.Daemon"
        ).await?;
        
        Ok(Self { connection, proxy })
    }
    
    /// Ping the daemon
    pub async fn ping(&self) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Ping", &()).await?;
        Ok(reply)
    }
    
    /// Get system status
    pub async fn status(&self) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Status", &()).await?;
        Ok(reply)
    }
    
    /// Install packages
    pub async fn install_packages(&self, packages: Vec<String>, yes: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("InstallPackages", &(packages, yes, dry_run)).await?;
        Ok(reply)
    }
    
    /// Remove packages
    pub async fn remove_packages(&self, packages: Vec<String>, yes: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("RemovePackages", &(packages, yes, dry_run)).await?;
        Ok(reply)
    }
    
    /// Upgrade system
    pub async fn upgrade_system(&self, yes: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("UpgradeSystem", &(yes, dry_run)).await?;
        Ok(reply)
    }
    
    /// Rollback system
    pub async fn rollback(&self, yes: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Rollback", &(yes, dry_run)).await?;
        Ok(reply)
    }
    
    /// List packages
    pub async fn list_packages(&self) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("ListPackages", &()).await?;
        Ok(reply)
    }
    
    /// Search packages
    pub async fn search_packages(&self, query: String, verbose: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("SearchPackages", &(query, verbose)).await?;
        Ok(reply)
    }
    
    /// Show package info
    pub async fn show_package_info(&self, package: String) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("ShowPackageInfo", &(package)).await?;
        Ok(reply)
    }
    
    /// Show history
    pub async fn show_history(&self, verbose: bool, limit: u32) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("ShowHistory", &(verbose, limit)).await?;
        Ok(reply)
    }
    
    /// Checkout to different branch/commit
    pub async fn checkout(&self, target: String, yes: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Checkout", &(target, yes, dry_run)).await?;
        Ok(reply)
    }
    
    /// Prune deployments
    pub async fn prune_deployments(&self, keep: u32, yes: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("PruneDeployments", &(keep, yes, dry_run)).await?;
        Ok(reply)
    }
    
    /// Initialize system
    pub async fn initialize(&self, branch: String) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Initialize", &(branch)).await?;
        Ok(reply)
    }

    /// Deploy a specific commit
    pub async fn deploy(&self, commit: String, reboot: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Deploy", &(commit, reboot, dry_run)).await?;
        Ok(reply)
    }

    /// Enhanced rollback with OSTree integration
    pub async fn rollback_enhanced(&self, reboot: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("RollbackEnhanced", &(reboot, dry_run)).await?;
        Ok(reply)
    }

    /// Enhanced upgrade with OSTree integration
    pub async fn upgrade_enhanced(&self, reboot: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("UpgradeEnhanced", &(reboot, dry_run)).await?;
        Ok(reply)
    }

    /// Reset to base deployment
    pub async fn reset(&self, reboot: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Reset", &(reboot, dry_run)).await?;
        Ok(reply)
    }

    /// Rebase to different tree
    pub async fn rebase(&self, refspec: String, reboot: bool, allow_downgrade: bool, skip_purge: bool, dry_run: bool) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("Rebase", &(refspec, reboot, allow_downgrade, skip_purge, dry_run)).await?;
        Ok(reply)
    }

    /// Reload configuration
    pub async fn reload_configuration(&self) -> Result<String, Box<dyn Error>> {
        let reply: String = self.proxy.call("ReloadConfiguration", &()).await?;
        Ok(reply)
    }
}

/// Helper function to call daemon with fallback to client
pub async fn call_daemon_with_fallback<F, T>(
    daemon_call: F,
    client_fallback: T,
) -> Result<String, Box<dyn Error>>
where
    F: FnOnce(&DaemonClient) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String, Box<dyn Error>>> + Send>>,
    T: FnOnce() -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String, Box<dyn Error>>> + Send>>,
{
    match DaemonClient::new().await {
        Ok(client) => {
            match daemon_call(&client).await {
                Ok(result) => Ok(result),
                Err(e) => {
                    eprintln!("Warning: Daemon call failed: {}. Falling back to client...", e);
                    client_fallback().await
                }
            }
        }
        Err(e) => {
            eprintln!("Warning: Could not connect to daemon: {}. Falling back to client...", e);
            client_fallback().await
        }
    }
} 