use std::sync::atomic::Ordering;
use std::time::Instant;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;

use super::{run_cmd, DEMO_MODE, Job, UiSender};

/// Sistem dosyalarını kopyala (rsync).
pub struct CopyFilesJob {
    /// Canlı sistemin kök dizini; ör. "/run/livecd/squashfs-root/"
    pub source: String,
    /// Hedef bağlama noktası; ör. "/mnt"
    pub target: String,
}

#[async_trait::async_trait]
impl Job for CopyFilesJob {
    fn name(&self) -> &str { "job_copy_files" }

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        ui.log(format!("[DEBUG] CopyFilesJob başladı"));
        ui.log(format!("[DEBUG] Kaynak: '{}'", self.source));
        ui.log(format!("[DEBUG] Hedef:  '{}'", self.target));

        // Kaynak dizini kontrol et
        let source_path = std::path::Path::new(&self.source);
        let source_exists = source_path.exists();
        let source_readable = source_exists && source_path.is_dir();
        ui.log(format!("[DEBUG] Kaynak mevcut: {}, dizin: {}", source_exists, source_readable));

        if !source_exists {
            ui.log(format!("[DEBUG] Kaynak dizin içeriği aranıyor..."));
            // Kaynağın bir üst dizinini dene
            let parent = source_path.parent();
            if let Some(p) = parent {
                if p.exists() {
                    if let Ok(entries) = std::fs::read_dir(p) {
                        for entry in entries.flatten().take(20) {
                            ui.log(format!("[DEBUG]  /{:?}", entry.path()));
                        }
                    }
                }
            } else {
                ui.log("[DEBUG] Kaynağın parent'ı yok (kök dizin olabilir)");
            }
        } else if source_readable {
            // İlk 10 girdiyi listele
            if let Ok(entries) = std::fs::read_dir(source_path) {
                let mut count = 0;
                for entry in entries.flatten().take(10) {
                    ui.log(format!("[DEBUG]  {:?}", entry.path()));
                    count += 1;
                }
                ui.log(format!("[DEBUG] (ilk {} girdi listelendi)", count));
            }
        }

        ui.log(format!("rsync {} → {}", self.source, self.target));
        ui.copy_started();

        let is_root_source = self.source.trim_end_matches('/') == "";

        // ── rsync exclude'leri ──
        // Aynı liste hem rsync'e hem de aşağıdaki bilgilendirme amaçlı find
        // sayacına uygulanır. İlerleme artık rsync'in kendi bayt-tabanlı
        // yüzdesinden (--info=progress2) alındığı için sayım farkları barla
        // ilgili değildir; find yalnızca "N dosya kopyalanacak" logu içindir.
        let mut excludes: Vec<String> = vec![
            "/proc/**".to_string(),
            "/sys/**".to_string(),
            "/dev/**".to_string(),
            "/run/**".to_string(),
            "/tmp/**".to_string(),
            "/var/tmp/**".to_string(),
            "/var/cache/**".to_string(),
            "/home/**".to_string(),
            "/root/**".to_string(),
            "/lost+found".to_string(),
            "*.sqfs".to_string(),
        ];

        // Kaynak canlı sistem köküyse ekstra exclude'ler
        if is_root_source {
            excludes.extend_from_slice(&[
                "/bootmnt/**".to_string(),
                "/ro_branch/**".to_string(),
                "/mnt/**".to_string(),
                "/media/**".to_string(),
                "/var/log/journal/**".to_string(),
                "/var/lib/pisi/cache/**".to_string(),
                "/var/lib/pisi/archives/**".to_string(),
                "/etc/adjtime".to_string(),
                "/etc/mtab".to_string(),
                "/etc/resolv.conf".to_string(),
                "/etc/hostname".to_string(),
                "/etc/machine-id".to_string(),
            ]);
        }

        // ── Önce find ile toplam dosya sayısını bul (yalnızca bilgilendirme) ──
        let find_path = self.source.trim_end_matches('/');
        let find_path = if find_path.is_empty() { "/" } else { find_path };
        let _total_files = {
            let mut cmd = Command::new("find");
            cmd.arg(find_path);
            if is_root_source {
                cmd.arg("-xdev");
            }
            for ex in &excludes {
                if let Some(dir) = ex.strip_suffix("/**") {
                    cmd.args(["-path", dir, "-prune", "-o"]);
                } else if ex.starts_with('/') {
                    cmd.args(["-path", ex.as_str(), "-prune", "-o"]);
                } else {
                    cmd.args(["-name", ex.as_str(), "-prune", "-o"]);
                }
            }
            cmd.args(["-type", "f", "-print"]);
            cmd.stdout(std::process::Stdio::piped());
            cmd.stderr(std::process::Stdio::null());
            match cmd.spawn() {
                Ok(mut c) => {
                    let stdout = c.stdout.take()
                        .ok_or_else(|| "find stdout alınamadı".to_string())?;
                    let r = BufReader::new(stdout);
                    let mut lines = r.lines();
                    let mut n: u64 = 0;
                    while let Ok(Some(_)) = lines.next_line().await {
                        n += 1;
                    }
                    let _ = c.wait().await;
                    if n > 0 {
                        ui.log(format!("{} dosya kopyalanacak", n));
                    }
                    n
                }
                Err(_) => 0
            }
        };

        let mut args: Vec<String> = vec![
            "-aAXH".to_string(),
            "--out-format=%n".to_string(),
            // Bayt-tabanlı ilerleme (stdout'a \r ile karışır). rsync kendisi
            // hesapladığı için %100'e ancak kopyalama gerçekten bitince ulaşır.
            "--info=progress2".to_string(),
            // Incremental recursion (varsayılan) dosya listesini aktarım
            // sırasında büyütür; toplam bayt revize olunca yüzde geriye
            // düşebilir (%98 → %65 gibi). Liste önden tam kurulunca yüzde
            // monoton ilerler.
            "--no-inc-recursive".to_string(),
        ];
        for ex in &excludes {
            args.push(format!("--exclude={}", ex));
        }

        if let (Ok(source_path), Ok(target_path)) = (
            std::path::Path::new(&self.source).canonicalize(),
            std::path::Path::new(&self.target).canonicalize(),
        ) {
            if let Ok(relative_target) = target_path.strip_prefix(&source_path) {
                if !relative_target.as_os_str().is_empty() {
                    // İçerik hariç tutulur ama dizinin kendisi boş olarak kalır
                    // (ör. /mnt). Çıplak desen kullanılırsa dizinin kendisi de
                    // budanır ve kurulu sistemde /mnt hiç oluşmaz.
                    args.push(format!("--exclude={}/**", relative_target.display()));
                }
            }
        }

        args.push(self.source.clone());
        args.push(self.target.clone());

        if DEMO_MODE.load(Ordering::Relaxed) {
            ui.log("rsync /run/livecd/squashfs-root/ → /mnt (DEMO)");
            let steps = 20;
            for i in 0..steps {
                let p = (i + 1) as f32 / steps as f32;
                ui.current_file(format!("/usr/share/doc/{}/README.md", (i * 137) % 50));
                ui.sub_progress(p);
                tokio::time::sleep(std::time::Duration::from_millis(150)).await;
            }
            ui.copy_finished();
            return Ok(());
        }

        let mut child = Command::new("rsync")
            .args(&args)
            .stdout(std::process::Stdio::piped())
            .stderr(std::process::Stdio::piped())
            .spawn()
            .map_err(|e| format!("rsync çalıştırılamadı: {}", e))?;

        let stdout = child.stdout.take()
            .ok_or_else(|| "rsync stdout alınamadı".to_string())?;
        let stderr = child.stderr.take()
            .ok_or_else(|| "rsync stderr alınamadı".to_string())?;

        // Stderr'i arka planda oku (hata birikimi ve tampon taşmasını engelle).
        // progress2 satırları stdout'a karıştığı için yalnızca hata metni toplanır.
        let stderr_buf = std::sync::Arc::new(tokio::sync::Mutex::new(String::new()));
        let stderr_buf_clone = stderr_buf.clone();
        tokio::spawn(async move {
            let r = BufReader::new(stderr);
            let mut lines = r.lines();
            while let Ok(Some(l)) = lines.next_line().await {
                let mut b = stderr_buf_clone.lock().await;
                b.push_str(&l);
                b.push('\n');
            }
        });

        // Stdout: --out-format=%n ile dosya adları, --info=progress2 ile
        // bayt-tabanlı ilerleme satırları aynı akışa, \r ve \n ayraçlarıyla
        // karışık olarak yazılır. Her parçayı içeriğine göre sınıflandırırız:
        // "%" içerenler ilerleme (yüzde), diğerleri dosya adıdır.
        let reader = BufReader::new(stdout);
        let mut lines = reader.lines();
        let mut file_count: u64 = 0;
        // Son gönderilen yüzde — barın geriye düşmemesi için (--no-inc-recursive
        // ile gerekmemesi gerekir ama yine de garanti).
        let mut last_sub: f32 = 0.0;
        // Son UI güncellemesi — çok sık güncelleme GPU (özellikle Intel UMA) baskısını azaltır
        let mut last_update = Instant::now();

        while let Some(line) = lines.next_line().await
            .map_err(|e| format!("rsync çıktısı okunamadı: {}", e))?
        {
            for segment in line.split('\r') {
                let trimmed = segment.trim();
                if trimmed.is_empty() {
                    continue;
                }

                if trimmed.contains('%') {
                    if let Some(pct) = parse_progress_percent(trimmed) {
                        if pct >= last_sub {
                            last_sub = pct;
                            ui.sub_progress(pct);
                        }
                    }
                    continue;
                }

                file_count += 1;

                let now = Instant::now();
                if now.duration_since(last_update) >= std::time::Duration::from_millis(50)
                    || file_count == 1
                    || trimmed.contains("Packages") || trimmed.contains("package")
                {
                    ui.current_file(trimmed.to_string());
                    last_update = now;
                }
            }
        }

        let status = child.wait().await
            .map_err(|e| format!("rsync beklenemedi: {}", e))?;

        if !status.success() {
            let err = stderr_buf.lock().await.clone();
            return Err(format!(
                "rsync çıkış kodu: {:?}. Hata: {}",
                status.code(),
                err.trim()
            ));
        }

        ui.copy_finished();
        Ok(())
    }
}

/// /proc, /sys, /dev, /dev/pts dizinlerini chroot hedefine bind-mount yapar.
/// Standart `chroot` için gereklidir; kurulum başında çağrılmalıdır.
pub struct MountBindJob {
    pub mount: String,  // ör. "/mnt"
}

#[async_trait::async_trait]
impl Job for MountBindJob {
    fn name(&self) -> &str { "job_mount_bind" }

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let mt = &self.mount;
        // (kaynak, hedef) — kaynak sistem üzerindeki gerçek path
        let bind_dirs = [
            ("/proc",     format!("{}/proc",    mt)),
            ("/sys",      format!("{}/sys",     mt)),
            ("/dev",      format!("{}/dev",     mt)),
            ("/dev/pts",  format!("{}/dev/pts", mt)),
            ("/run",      format!("{}/run",     mt)), // COMAR/dbus için gerekli
        ];
        for (src, target) in &bind_dirs {
            tokio::fs::create_dir_all(target).await
                .map_err(|e| format!("{} oluşturulamadı: {}", target, e))?;
            ui.log(format!("mount --bind {} {}", src, target));
            run_cmd("mount", &["--bind", src, target]).await?;
        }

        // efivarfs — UEFI kurulumunda grub-install için zorunlu
        let efivarfs_target = format!("{}/sys/firmware/efi/efivars", mt);
        if std::path::Path::new("/sys/firmware/efi/efivars").exists() {
            tokio::fs::create_dir_all(&efivarfs_target).await
                .map_err(|e| format!("{} oluşturulamadı: {}", efivarfs_target, e))?;
            ui.log(format!("mount --bind /sys/firmware/efi/efivars {}", efivarfs_target));
            run_cmd("mount", &["--bind", "/sys/firmware/efi/efivars", &efivarfs_target]).await?;
        }
        Ok(())
    }
}

/// Chroot ve disk bağlantılarını temizler (kurulum sonunda çağrılır).
///
/// Önce bind-mount'lar, ardından gerçek bölümler (/boot/efi ve kök)
/// ters sırada unmount edilir. FAT32 (ESP) backup boot sektörü yalnızca
/// umount anında güncellendiği için bölümler `-lf` ile hemen lazy
/// unmount edilmez; önce temiz `umount` denenir, sadece "busy" durumunda
/// `umount -lf` ile son çareye düşülür.
pub struct UnmountBindJob {
    pub mount: String,
}

#[async_trait::async_trait]
impl Job for UnmountBindJob {
    fn name(&self) -> &str { "job_umount_bind" }

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let mt = &self.mount;
        // Ters sırada umount — en derin path önce
        let subdirs = [
            "sys/firmware/efi/efivars",
            "dev/pts",
            "dev",
            "run",
            "sys",
            "proc",
            "boot/efi",
        ];
        let mut errors: Vec<String> = Vec::new();

        // Bind-mount'lar + ESP (/boot/efi)
        for subdir in &subdirs {
            let target = format!("{}/{}", mt, subdir);
            if !umount_clean(&target, ui).await {
                let msg = format!("{} temiz unmount edilemedi", target);
                errors.push(msg);
            }
        }

        // Kök bölüm: chroot mount'unun kendisi
        if !umount_clean(mt, ui).await {
            let msg = format!("{} (kök) temiz unmount edilemedi", mt);
            errors.push(msg);
        }

        if !errors.is_empty() {
            return Err(format!(
                "Mount temizleme hatası — bazı dosya sistemleri bağlı kalmış olabilir:\n{}",
                errors.join("\n")
            ));
        }
        Ok(())
    }
}

/// Tek bir mount noktasını önce temiz `umount` ile, başarısızsa `umount -lf`
/// (lazy) ile çözer. "not mounted" / "no such file" gibi beklenen durumlar
/// kritik sayılmaz; yalnızca komut çalıştırılamaması gerçek hatadır.
async fn umount_clean(target: &str, ui: &UiSender) -> bool {
    ui.log(format!("umount {}", target));
    match tokio::process::Command::new("umount")
        .arg(target).status().await
    {
        Ok(status) if status.success() => true,
        Ok(status) => {
            ui.log(format!("⚠ umount temiz deneme başarısız (kod {}): {} → lazy deneniyor", status, target));
            match tokio::process::Command::new("umount")
                .args(["-lf", target]).status().await
            {
                Ok(s) if s.success() => true,
                Ok(s) => {
                    // "not mounted" / "no such file" beklenen durumlar
                    ui.log(format!("⚠ umount uyarısı (kritik değil, kod {}): {}", s, target));
                    true
                }
                Err(e) => {
                    let msg = format!("umount komutu başlatılamadı ({}): {}", target, e);
                    ui.log(format!("✗ {}", msg));
                    false
                }
            }
        }
        Err(e) => {
            let msg = format!("umount komutu başlatılamadı ({}): {}", target, e);
            ui.log(format!("✗ {}", msg));
            false
        }
    }
}

/// rsync `--info=progress2` satırından bayt-tabanlı yüzdeyi çıkarır.
/// Örnek satır:
///   "      1,234,567  45%  3.21MB/s    0:00:05 (xfr#1, to-chk=999/1000)"
/// Dosya adı bazlı tahminin aksine bu yüzde gerçek aktarılan veriyi yansıtır
/// ve %100'e ancak kopyalama tamamlanınca ulaşır.
fn parse_progress_percent(line: &str) -> Option<f32> {
    let pct_pos = line.rfind('%')?;
    let before = &line[..pct_pos];
    let token = before
        .rsplit(|c: char| c.is_whitespace())
        .find(|t| !t.is_empty())?;
    let digits: String = token.chars().filter(|c| c.is_ascii_digit()).collect();
    let n: f32 = digits.parse().ok()?;
    Some((n / 100.0).clamp(0.0, 1.0))
}

#[cfg(test)]
mod tests {
    use super::parse_progress_percent;

    #[test]
    fn parses_progress2_percent() {
        // Gerçek rsync --info=progress2 çıktı parçaları
        assert_eq!(
            parse_progress_percent("         32.768   0%    0,00kB/s    0:00:00 "),
            Some(0.0)
        );
        assert_eq!(
            parse_progress_percent("     20.971.520  99%  587,32MB/s    0:00:00 (xfr#1, to-chk=3/5)"),
            Some(0.99)
        );
        assert_eq!(
            parse_progress_percent("     21.022.720  100%  571,93MB/s    0:00:00 (xfr#2, to-chk=0/5)"),
            Some(1.0)
        );
        // Dosya adları yüzde içermez → None
        assert_eq!(parse_progress_percent("big.bin"), None);
        assert_eq!(parse_progress_percent("sub/"), None);
    }
}
