// install_queue.rs — background install/remove queue with real progress tracking

use std::cell::RefCell;
use std::collections::{HashSet, VecDeque};
use std::sync::{mpsc, Arc, Mutex};

/// One pending operation.
#[derive(Clone)]
pub struct QueueEntry {
    pub app_name: String,
    pub app_id: String,
    pub source: String,
    pub remote: String,
    pub is_remove: bool,
    pub icon_path: String,
    pub icon_url: String,
    pub user_remote: bool,
}

/// Banner update sent from the worker to the main-thread poll timer.
#[derive(Clone)]
pub enum BannerUpdate {
    /// A new operation became active.
    Active {
        app_name: String,
        is_remove: bool,
        queue_behind: usize,
        icon_path: String,
        icon_url: String,
    },
    /// Real progress 0.0–1.0 parsed from the stream.
    Progress(f64),
    /// All operations done — hide the banner.
    Idle,
}

// ── Progress parsing (mirrors updates.rs) ────────────────────────────────────

/// Parse [N/M] bracket fraction from dnf5 / flatpak output lines.
fn parse_progress(line: &str) -> Option<f64> {
    let trimmed = line.trim();
    if let Some(start) = trimmed.find('[') {
        if let Some(end) = trimmed[start..].find(']') {
            let inner = &trimmed[start + 1..start + end];
            if let Some(slash) = inner.find('/') {
                let n: f64 = inner[..slash].trim().parse().ok()?;
                let total: f64 = inner[slash + 1..].trim().parse().ok()?;
                if total > 0.0 {
                    return Some((n / total).min(1.0));
                }
            }
        }
    }
    // "N of M" — "Fetching 3 of 50"
    if let Some(pos) = line.find(" of ") {
        let n_str = line[..pos].trim().split_whitespace().last().unwrap_or("");
        let m_str = line[pos + 4..].trim().split_whitespace().next().unwrap_or("");
        if let (Ok(n), Ok(m)) = (n_str.parse::<f64>(), m_str.parse::<f64>()) {
            if m > 0.0 {
                return Some((n / m).min(1.0));
            }
        }
    }
    None
}

// ── Shared status (worker writes, main-thread reads) ──────────────────────────

#[derive(Default)]
pub struct QueueStatus {
    pub active_id: Option<String>,
    pub pending_ids: HashSet<String>,
}

impl QueueStatus {
    pub fn contains(&self, app_id: &str) -> bool {
        self.active_id.as_deref() == Some(app_id) || self.pending_ids.contains(app_id)
    }
    pub fn is_active(&self, app_id: &str) -> bool {
        self.active_id.as_deref() == Some(app_id)
    }
}

// ── Public queue handle ───────────────────────────────────────────────────────

pub struct QueueHandleInner {
    tx: mpsc::SyncSender<QueueEntry>,
    pub status: Arc<Mutex<QueueStatus>>,
}

impl QueueHandleInner {
    pub fn enqueue(&self, entry: QueueEntry) {
        {
            let mut s = self.status.lock().unwrap();
            s.pending_ids.insert(entry.app_id.clone());
        }
        let _ = self.tx.send(entry);
    }

    pub fn contains(&self, app_id: &str) -> bool {
        self.status.lock().unwrap().contains(app_id)
    }

    pub fn is_active(&self, app_id: &str) -> bool {
        self.status.lock().unwrap().is_active(app_id)
    }
}

pub type QueueHandle = Arc<QueueHandleInner>;

// ── Thread-local singleton ────────────────────────────────────────────────────

thread_local! {
    static HANDLE: RefCell<Option<QueueHandle>> = RefCell::new(None);
}

pub fn get_queue() -> Option<QueueHandle> {
    HANDLE.with(|h| h.borrow().clone())
}

// ── Initialization ────────────────────────────────────────────────────────────

pub fn init_queue() -> mpsc::Receiver<BannerUpdate> {
    let (work_tx, work_rx) = mpsc::sync_channel::<QueueEntry>(128);
    let (banner_tx, banner_rx) = mpsc::channel::<BannerUpdate>();
    let status: Arc<Mutex<QueueStatus>> = Arc::default();

    let handle = Arc::new(QueueHandleInner {
        tx: work_tx,
        status: Arc::clone(&status),
    });

    HANDLE.with(|h| {
        *h.borrow_mut() = Some(handle);
    });

    std::thread::spawn(move || {
        worker_loop(work_rx, status, banner_tx);
    });

    banner_rx
}

// ── Worker ────────────────────────────────────────────────────────────────────

fn worker_loop(
    rx: mpsc::Receiver<QueueEntry>,
    status: Arc<Mutex<QueueStatus>>,
    banner_tx: mpsc::Sender<BannerUpdate>,
) {
    let mut queue = VecDeque::<QueueEntry>::new();

    loop {
        match rx.recv() {
            Ok(e) => queue.push_back(e),
            Err(_) => break,
        }
        while let Ok(e) = rx.try_recv() {
            queue.push_back(e);
        }

        while let Some(entry) = queue.pop_front() {
            {
                let mut s = status.lock().unwrap();
                s.pending_ids.remove(&entry.app_id);
                s.active_id = Some(entry.app_id.clone());
            }
            let _ = banner_tx.send(BannerUpdate::Active {
                app_name: entry.app_name.clone(),
                is_remove: entry.is_remove,
                queue_behind: queue.len(),
                icon_path: entry.icon_path.clone(),
                icon_url: entry.icon_url.clone(),
            });

            run_entry(&entry, &banner_tx);

            // Drain new items that arrived while running.
            while let Ok(e) = rx.try_recv() {
                {
                    let mut s = status.lock().unwrap();
                    s.pending_ids.insert(e.app_id.clone());
                }
                queue.push_back(e);
            }

            {
                let mut s = status.lock().unwrap();
                s.active_id = None;
            }

            if !queue.is_empty() {
                let next = queue.front().unwrap();
                let _ = banner_tx.send(BannerUpdate::Active {
                    app_name: next.app_name.clone(),
                    is_remove: next.is_remove,
                    queue_behind: queue.len() - 1,
                    icon_path: next.icon_path.clone(),
                    icon_url: next.icon_url.clone(),
                });
            }
        }

        let _ = banner_tx.send(BannerUpdate::Idle);
    }
}

/// Resolve an AppStream ID to an RPM package name.
/// Falls back to the raw ID if not found (handles native: prefix too).
fn resolve_package_name(app_id: &str) -> String {
    let cache = rakuos_appstream::get_appstream();
    cache.get(app_id)
        .or_else(|| cache.get(&format!("native:{}", app_id)))
        .and_then(|a| if a.package_name.is_empty() { None } else { Some(a.package_name.clone()) })
        .unwrap_or_else(|| app_id.to_string())
}

fn run_flatpak_output(args: &[&str]) -> Vec<String> {
    let out = std::process::Command::new("flatpak")
        .args(args)
        .output();
    match out {
        Ok(o) => {
            let code = if o.status.success() { 0 } else { 1 };
            let mut lines: Vec<String> = String::from_utf8_lossy(&o.stdout)
                .lines()
                .filter(|l| !l.trim().is_empty())
                .map(|l| l.to_string())
                .collect();
            lines.push(format!("__done__{}", code));
            lines
        }
        Err(e) => vec![format!("Error: {}", e), "__done__1".to_string()],
    }
}

fn run_entry(entry: &QueueEntry, banner_tx: &mpsc::Sender<BannerUpdate>) {
    let scope = if entry.user_remote { "--user" } else { "--system" };
    let lines: Vec<String> = if entry.is_remove {
        if entry.source == "flatpak" || entry.source == "flatpak-runtime" {
            let mut args = vec!["uninstall", scope, "--noninteractive", "-y"];
            if entry.source == "flatpak-runtime" { args.push("--force-remove"); }
            args.push(entry.app_id.as_str());
            run_flatpak_output(&args)
        } else {
            let pkg = resolve_package_name(&entry.app_id);
            rakuos_packages::remove_stream(&pkg).collect()
        }
    } else if entry.source == "flatpak" {
        let remote = if entry.remote.is_empty() { "flathub" } else { entry.remote.as_str() };
        run_flatpak_output(&["install", scope, "--noninteractive", "-y", remote, entry.app_id.as_str()])
    } else {
        let pkg = resolve_package_name(&entry.app_id);
        rakuos_packages::install_stream(&pkg).collect()
    };

    for line in lines {
        if line.starts_with("__done__") {
            break;
        }
        if let Some(pct) = parse_progress(&line) {
            let _ = banner_tx.send(BannerUpdate::Progress(pct));
        }
    }
}
