//! Flatpak package manager operations

use std::process::{Command, Stdio};
use std::io::Read;

use crate::error::{ArcError, Result};
use crate::progress::{ProgressCallback, ProgressState};

/// Represents a Flatpak package with its bundle ID and name
#[derive(Debug, Clone)]
pub struct FlatpakPackage {
    pub bundle_id: String,
    pub name: String,
}

impl FlatpakPackage {
    /// Create a new FlatpakPackage from a bundle ID
    pub fn from_bundle_id(bundle_id: String) -> Self {
        let name = bundle_id
            .split('.')
            .last()
            .unwrap_or(&bundle_id)
            .to_lowercase();
        
        Self { bundle_id, name }
    }
}

/// Search for Flatpak packages
pub fn search(query: &str) -> Result<Vec<FlatpakPackage>> {
    let output = Command::new("flatpak")
        .arg("search")
        .arg(query)
        .output()?;

    if !output.status.success() {
        return Ok(Vec::new());
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let packages: Vec<FlatpakPackage> = stdout
        .lines()
        .filter_map(|line| {
            let parts: Vec<&str> = line.split('\t').collect();
            // Format: Name\tDescription\tApplication ID\tVersion\tBranch\tRemotes
            if parts.len() >= 3 {
                let bundle_id = parts[2].trim().to_string();
                if !bundle_id.is_empty() {
                    return Some(FlatpakPackage::from_bundle_id(bundle_id));
                }
            }
            None
        })
        .collect();

    Ok(packages)
}

/// Install a Flatpak package by bundle ID with progress callback
pub fn install_with_progress(
    bundle_id: &str,
    progress: &dyn ProgressCallback,
) -> Result<()> {
    progress.update(ProgressState::Preparing);
    
    let status = Command::new("flatpak")
        .arg("install")
        .arg("-y")
        .arg(bundle_id)
        .output()?
        .status;

    if !status.success() {
        progress.update(ProgressState::Failed(format!(
            "Failed to install flatpak package: {}",
            bundle_id
        )));
        return Err(ArcError::CommandFailed(format!(
            "Failed to install flatpak package: {}",
            bundle_id
        )));
    }

    progress.update(ProgressState::Completed);
    Ok(())
}

/// Install a Flatpak package by bundle ID
pub fn install(bundle_id: &str) -> Result<()> {
    let status = Command::new("flatpak")
        .arg("install")
        .arg("-y")
        .arg(bundle_id)
        .status()?;

    if !status.success() {
        return Err(ArcError::CommandFailed(format!(
            "Failed to install flatpak package: {}",
            bundle_id
        )));
    }

    Ok(())
}

/// Uninstall a Flatpak package by bundle ID with progress callback
pub fn uninstall_with_progress(
    bundle_id: &str,
    progress: &dyn ProgressCallback,
) -> Result<()> {
    progress.update(ProgressState::Preparing);
    
    let status = Command::new("flatpak")
        .arg("uninstall")
        .arg("-y")
        .arg(bundle_id)
        .output()?
        .status;

    if !status.success() {
        progress.update(ProgressState::Failed(format!(
            "Failed to uninstall flatpak package: {}",
            bundle_id
        )));
        return Err(ArcError::CommandFailed(format!(
            "Failed to uninstall flatpak package: {}",
            bundle_id
        )));
    }

    progress.update(ProgressState::Completed);
    Ok(())
}

/// Uninstall a Flatpak package by bundle ID
pub fn uninstall(bundle_id: &str) -> Result<()> {
    let status = Command::new("flatpak")
        .arg("uninstall")
        .arg("-y")
        .arg(bundle_id)
        .status()?;

    if !status.success() {
        return Err(ArcError::CommandFailed(format!(
            "Failed to uninstall flatpak package: {}",
            bundle_id
        )));
    }

    Ok(())
}

/// Check if a Flatpak is installed
pub fn is_installed(bundle_id: &str) -> Result<bool> {
    let output = Command::new("flatpak")
        .arg("info")
        .arg(bundle_id)
        .output()?;

    Ok(output.status.success())
}

/// List all installed Flatpak packages
pub fn list_installed() -> Result<Vec<FlatpakPackage>> {
    let output = Command::new("flatpak")
        .arg("list")
        .arg("--app")
        .arg("--columns=application")
        .output()?;

    if !output.status.success() {
        return Err(ArcError::CommandFailed(
            "Failed to list installed flatpak packages".to_string(),
        ));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let packages: Vec<FlatpakPackage> = stdout
        .lines()
        .filter(|line| !line.is_empty())
        .map(|line| FlatpakPackage::from_bundle_id(line.trim().to_string()))
        .collect();

    Ok(packages)
}

/// Install a Flatpak package (silent, output captured)
pub fn install_silent(bundle_id: &str) -> Result<(bool, String)> {
    let mut child = Command::new("flatpak")
        .arg("install")
        .arg("-y")
        .arg(bundle_id)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    let mut stdout = String::new();
    let mut stderr = String::new();

    if let Some(mut out) = child.stdout.take() {
        let _ = out.read_to_string(&mut stdout);
    }
    if let Some(mut err) = child.stderr.take() {
        let _ = err.read_to_string(&mut stderr);
    }

    let status = child.wait()?;
    let output = format!("{}{}", stdout, stderr);

    Ok((status.success(), output))
}

/// Uninstall a Flatpak package (silent, output captured)
pub fn uninstall_silent(bundle_id: &str) -> Result<(bool, String)> {
    let mut child = Command::new("flatpak")
        .arg("uninstall")
        .arg("-y")
        .arg(bundle_id)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;

    let mut stdout = String::new();
    let mut stderr = String::new();

    if let Some(mut out) = child.stdout.take() {
        let _ = out.read_to_string(&mut stdout);
    }
    if let Some(mut err) = child.stderr.take() {
        let _ = err.read_to_string(&mut stderr);
    }

    let status = child.wait()?;
    let output = format!("{}{}", stdout, stderr);

    Ok((status.success(), output))
}
