//! Kurulum iş kuyruğu.
//!
//! Mimari:
//!   egui (ana iş parçacığı)
//!     └─► std::thread::spawn
//!           └─► tokio::runtime::Runtime::block_on
//!                 └─► JobQueue::run_all  ─► std::sync::mpsc::Sender<InstallMessage>
//!
//! egui her frame'de `Receiver::try_recv` ile kuyruktan mesaj okur.
//! `egui::Context::request_repaint()` arka plan iş parçacığından çağrılarak
//! UI'nin hemen güncellenmesi sağlanır.


use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use rust_i18n::t;

// ─────────────────────────────────────────────
// MESAJ TİPİ
// ─────────────────────────────────────────────

/// Arka plan iş parçacığından UI'ye gönderilen mesajlar.
#[derive(Debug)]
pub enum InstallMessage {
    /// Bir log satırı.
    Log(String),
    /// Genel ilerleme; 0.0–1.0 arasında.
    Progress(f32),
    /// Alt ilerleme (ör: rsync içindeki dosya kopyalama); 0.0–1.0 arasında.
    SubProgress(f32),
    /// Kopyalanmakta olan dosya adı.
    CurrentFile(String),
    /// Tüm işler başarıyla tamamlandı.
    Done,
    /// Bir iş başarısız oldu; kurulum durdu.
    Error(String),
}

/// `mpsc::Sender<InstallMessage>` ile `egui::Context` birlikte taşır.
/// Her mesaj gönderiminde `request_repaint()` çağrısı UI'yi uyandırır.
#[derive(Clone)]
pub struct UiSender {
    tx: mpsc::Sender<InstallMessage>,
    ctx: eframe::egui::Context,
}

impl UiSender {
    pub fn new(tx: mpsc::Sender<InstallMessage>, ctx: eframe::egui::Context) -> Self {
        Self { tx, ctx }
    }

    pub fn send(&self, msg: InstallMessage) {
        let _ = self.tx.send(msg);
        self.ctx.request_repaint();
    }

    pub fn log(&self, s: impl Into<String>) {
        self.send(InstallMessage::Log(s.into()));
    }

    pub fn sub_progress(&self, p: f32) {
        self.send(InstallMessage::SubProgress(p));
    }

    pub fn current_file(&self, f: impl Into<String>) {
        self.send(InstallMessage::CurrentFile(f.into()));
    }
}

// ─────────────────────────────────────────────
// JOB TRAIT
// ─────────────────────────────────────────────

#[async_trait::async_trait]
pub trait Job: Send + Sync {
    fn name(&self) -> &str;
    /// İşi asenkron olarak çalıştırır.
    /// Hata durumunda `Err(açıklama)` döner; kuyruk durur.
    async fn run(&self, ui: &UiSender) -> Result<(), String>;
}

// ─────────────────────────────────────────────
// JOB QUEUE
// ─────────────────────────────────────────────

pub struct JobQueue {
    jobs: Vec<Box<dyn Job>>,
}

impl JobQueue {
    pub fn new(jobs: Vec<Box<dyn Job>>) -> Self {
        Self { jobs }
    }

    /// Tüm işleri sırayla çalıştırır.
    /// İlerleme ve loglar `ui` üzerinden UI'ye iletilir.
    pub async fn run_all(&mut self, ui: UiSender) {
        let total = self.jobs.len();

        for (i, job) in self.jobs.iter().enumerate() {
            let localized_name = t!(job.name());
            ui.log(format!("▶ {} başlatıldı…", localized_name));

            match job.run(&ui).await {
                Ok(()) => {
                    ui.log(format!("✓ {} tamamlandı.", localized_name));
                }
                Err(e) => {
                    let msg = format!("✗ {} başarısız: {}", localized_name, e);
                    ui.log(msg.clone());
                    ui.send(InstallMessage::Error(msg));
                    return;
                }
            }

            let progress = (i + 1) as f32 / total as f32;
            ui.send(InstallMessage::Progress(progress));
        }

        ui.send(InstallMessage::Done);
    }
}

// ─────────────────────────────────────────────
// SOMUT JOB'LAR
// ─────────────────────────────────────────────

/// 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!("rsync {} → {}", self.source, self.target));

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

        let mut args: Vec<String> = vec![
            "-aAXH".to_string(),
            "--info=progress2".to_string(),
            "--exclude=/proc/**".to_string(),
            "--exclude=/sys/**".to_string(),
            "--exclude=/dev/**".to_string(),
            "--exclude=/run/**".to_string(),
            "--exclude=/tmp/**".to_string(),
            "--exclude=/var/tmp/**".to_string(),
            "--exclude=/var/cache/**".to_string(),
            "--exclude=/home/**".to_string(),
            "--exclude=/root/**".to_string(),
            "--exclude=/lost+found".to_string(),
            "--exclude=*.sqfs".to_string(),
        ];

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

        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() {
                    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;
            }
            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 stderr = child.stderr.take()
            .ok_or_else(|| "rsync stderr alınamadı".to_string())?;
        let reader = BufReader::new(stderr);
        let mut lines = reader.lines();

        while let Some(line) = lines.next_line().await
            .map_err(|e| format!("rsync çıktısı okunamadı: {}", e))?
        {
            let trimmed = line.trim();
            // rsync --info=progress2 format (newlines when piped):
            //   1,024 100%  512.00kB/s  0:00:00 (xfr#5, to-chk=99/105)
            // Extract xfr# and to-chk for sub-progress
            if let Some(xfr_pos) = trimmed.find("xfr#") {
                let after_xfr = &trimmed[xfr_pos + 4..];
                let xfr_end = after_xfr.find(|c: char| !c.is_ascii_digit()).unwrap_or(after_xfr.len());
                let xfr: u64 = after_xfr[..xfr_end].parse().unwrap_or(0);

                if let Some(to_chk_pos) = trimmed.find("to-chk=") {
                    let after_to = &trimmed[to_chk_pos + 7..];
                    let slash_pos = after_to.find('/').unwrap_or(after_to.len());
                    let total_end = after_to[slash_pos + 1..]
                        .find(|c: char| !c.is_ascii_digit())
                        .unwrap_or(after_to[slash_pos + 1..].len());
                    let total: u64 = after_to[slash_pos + 1..][..total_end].parse().unwrap_or(1);

                    let p = if total > 0 {
                        (xfr as f32) / (total as f32)
                    } else {
                        0.0
                    };
                    ui.sub_progress(p.min(1.0));
                }
            }

            // Extract filename: lines starting with a path (not a number/space)
            let first_char = trimmed.chars().next();
            if let Some('/') = first_char {
                ui.current_file(trimmed.to_string());
            } else if trimmed.starts_with("./") || trimmed.starts_with("..") {
                ui.current_file(trimmed.to_string());
            }
        }

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

        if !status.success() {
            return Err(format!("rsync çıkış kodu: {:?}", status.code()));
        }

        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(())
    }
}

/// Bind-mount'ları temizler (kurulum sonunda çağrı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",
        ];
        let mut errors: Vec<String> = Vec::new();
        for subdir in &subdirs {
            let target = format!("{}/{}", mt, subdir);
            ui.log(format!("umount {}", target));
            match tokio::process::Command::new("umount")
                .arg("-lf").arg(&target).status().await
            {
                Ok(status) if status.success() => {}
                Ok(status) => {
                    // "not mounted" veya "no such file" hataları beklenen durumlar —
                    // bunları uyarı olarak logla ama kritik hata sayma.
                    ui.log(format!("⚠ umount uyarısı (kritik değil, kod {}): {}", status, target));
                }
                Err(e) => {
                    // Komut çalıştırılamadı — bu gerçek bir sorun.
                    let msg = format!("umount komutu başlatılamadı ({}): {}", target, e);
                    ui.log(format!("✗ {}", msg));
                    errors.push(msg);
                }
            }
        }
        if !errors.is_empty() {
            return Err(format!(
                "Bind mount temizleme hatası — bazı sanal dosya sistemleri bağlı kalmış olabilir:\n{}",
                errors.join("\n")
            ));
        }
        Ok(())
    }
}

/// initramfs yeniler (GRUB öncesi zorunlu).
/// PisiLinux `dracut` kullanıyorsa `dracut -f --regenerate-all`,
/// mkinitcpio kullanıyorsa `mkinitcpio -P`.
pub struct GenerateInitramfsJob {
    pub mount: String,
    pub encrypt_root: bool,
    pub lvm_active: bool,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // LVM için initramfs'e lvm modülü/hook'u ekleyen konfigürasyon
        if self.lvm_active && !self.encrypt_root {
            ui.log("LVM algılandı: initramfs LVM ayarlanıyor…");

            // dracut: add lvm module
            let dracut_conf = format!("{}/etc/dracut.conf.d/99-lvm.conf", self.mount);
            let _ = tokio::fs::create_dir_all(format!("{}/etc/dracut.conf.d", self.mount)).await;
            let _ = tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" lvm \"\n").await;

            // mkinitcpio: add lvm2 hook
            // Hem HOOKS="..." hem HOOKS=(... ) sözdizimi desteklenir.
            let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
            if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
                if let Ok(content) = tokio::fs::read_to_string(&mkinitcpio_conf).await {
                    if content.contains("HOOKS=") && !content.contains("lvm2") {
                        let new_content = content
                            .replace("HOOKS=\"", "HOOKS=\"lvm2 ")
                            .replace("HOOKS=(", "HOOKS=(lvm2 ");
                        let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
                    }
                }
            }
        }

        // LUKS için initramfs'e cryptsetup ekleyen konfigürasyon
        if self.encrypt_root {
            ui.log("LUKS algılandı: initramfs cryptsetup ayarlanıyor…");

            // dracut: add crypt module
            let dracut_conf = format!("{}/etc/dracut.conf.d/99-luks.conf", self.mount);
            tokio::fs::create_dir_all(
                format!("{}/etc/dracut.conf.d", self.mount)
            ).await.map_err(|e| format!("dracut.conf.d oluşturulamadı: {}", e))?;
            tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" crypt lvm \"\n")
                .await.map_err(|e| format!("dracut luks conf yazılamadı: {}", e))?;

            // mkinitcpio: add encrypt hook
            // Hem HOOKS="..." hem HOOKS=(... ) sözdizimi desteklenir.
            let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
            if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
                let content = tokio::fs::read_to_string(&mkinitcpio_conf)
                    .await.unwrap_or_default();
                if content.contains("HOOKS=") && !content.contains("encrypt") {
                    let new_content = content
                        .replace("HOOKS=\"", "HOOKS=\"encrypt ")
                        .replace("HOOKS=(", "HOOKS=(encrypt ");
                    let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
                }
            }
        }

        // PisiLinux /sbin/mkinitramfs, dracut, mkinitcpio sırasıyla dene
        let cmds: &[&[&str]] = &[
            &["mkinitramfs", "-f"],
            &["dracut", "-f", "--regenerate-all"],
            &["mkinitcpio", "-P"],
        ];
        for cmd in cmds {
            if run_chroot(&self.mount, cmd).await.is_ok() {
                ui.log(format!("initramfs oluşturuldu ({})", cmd[0]));
                return Ok(());
            }
        }
        Err("initramfs oluşturulamadı: mkinitramfs, dracut, mkinitcpio denendi".to_string())
    }
}



/// GRUB bootloader kurulumu (UEFI veya BIOS).
pub struct InstallGrubJob {
    pub disk: String,
    pub mount: String,
    pub is_uefi: bool,
    pub boot_device: String,
    pub timeout: u32,
    pub password: String,
    pub kernel_options: String,
    pub encrypt_root: bool,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // Ana dizinleri oluştur (demo modunda rsync çalışmaz)
        let etc_default = format!("{}/etc/default", self.mount);
        let _ = tokio::fs::create_dir_all(&etc_default).await;
        let grub_d = format!("{}/etc/grub.d", self.mount);
        let _ = tokio::fs::create_dir_all(&grub_d).await;

        // GRUB şifresi (varsa) — grub-mkpasswd-pbkdf2 ile hash üretilir,
        // düz metin asla diske yazılmaz.
        if !self.password.is_empty() {
            ui.log("GRUB şifresi PBKDF2 ile hashleniyor…");

            // grub-mkpasswd-pbkdf2 şifreyi stdin'den iki kez okur (şifre + doğrulama)
            use tokio::io::AsyncWriteExt;
            let pw_input = format!("{}\n{}\n", self.password, self.password);
            let mut child = tokio::process::Command::new("grub-mkpasswd-pbkdf2")
                .stdin(std::process::Stdio::piped())
                .stdout(std::process::Stdio::piped())
                .stderr(std::process::Stdio::null())
                .spawn()
                .map_err(|e| format!("grub-mkpasswd-pbkdf2 başlatılamadı: {}", e))?;

            if let Some(mut stdin) = child.stdin.take() {
                stdin.write_all(pw_input.as_bytes()).await
                    .map_err(|e| format!("grub-mkpasswd-pbkdf2 stdin hatası: {}", e))?;
            }

            let output = child.wait_with_output().await
                .map_err(|e| format!("grub-mkpasswd-pbkdf2 bekleme hatası: {}", e))?;

            // Çıktıdan "grub.pbkdf2.sha512...." token'ını ayrıştır
            let stdout = String::from_utf8_lossy(&output.stdout);
            let hash = stdout
                .lines()
                .find_map(|l| {
                    let pos = l.find("grub.pbkdf2")?;
                    Some(l[pos..].trim().to_string())
                })
                .ok_or("grub-mkpasswd-pbkdf2 çıktısında hash bulunamadı")?;

            let custom_grub = format!(
                "#!/bin/sh\nset superusers=\"root\"\npassword_pbkdf2 root {}\n",
                hash
            );
            let pw_path = format!("{}/etc/grub.d/40_custom", self.mount);
            tokio::fs::write(&pw_path, custom_grub)
                .await
                .map_err(|e| format!("GRUB şifre dosyası yazılamadı: {}", e))?;
            run_cmd("chmod", &["+x", &pw_path]).await?;
            ui.log("GRUB şifresi PBKDF2 hash olarak yazıldı.");
        }

        // GRUB timeout ayarı
        ui.log(format!("GRUB timeout ayarlanıyor: {}s", self.timeout));
        let default_path = format!("{}/etc/default/grub", self.mount);
        let mut grub_default = tokio::fs::read_to_string(&default_path)
            .await
            .unwrap_or_default();
        // timeout satırını bul/değiştir
        let timeout_line = format!("GRUB_TIMEOUT={}", self.timeout);
        let found_timeout = grub_default.lines()
            .any(|l| l.trim_start().starts_with("GRUB_TIMEOUT="));
        if found_timeout {
            let mut lines: Vec<String> = grub_default.lines()
                .map(|l| {
                    if l.trim_start().starts_with("GRUB_TIMEOUT=") {
                        timeout_line.clone()
                    } else {
                        l.to_string()
                    }
                })
                .collect();
            lines.push(String::new());
            grub_default = lines.join("\n");
        } else {
            grub_default.push_str(&format!("\n{}\n", timeout_line));
        }
        // kernel cmdline
        let mut cmdline = self.kernel_options.clone();
        if self.encrypt_root {
            // Kök bölüm UUID'sini blkid'den al (LUKS başlık UUID'si)
            let (root_num, _swap_num): (u32, u32) = if self.is_uefi { (3, 2) } else { (2, 1) };
            let root_part = part_path(&self.disk, root_num);
            let root_uuid = blkid_uuid(&root_part).await.unwrap_or_default();
            let crypt_arg = format!("cryptdevice=UUID={}:cryptroot root=/dev/mapper/cryptroot", root_uuid);
            if cmdline.is_empty() {
                cmdline = crypt_arg;
            } else {
                cmdline = format!("{} {}", crypt_arg, cmdline);
            }
        }
        if !cmdline.is_empty() {
            let cmdline_line = format!("GRUB_CMDLINE_LINUX=\"{}\"", cmdline);
            let found_cmdline = grub_default.lines()
                .any(|l| l.trim_start().starts_with("GRUB_CMDLINE_LINUX="));
            if found_cmdline {
                let mut lines: Vec<String> = grub_default.lines()
                    .map(|l| {
                        if l.trim_start().starts_with("GRUB_CMDLINE_LINUX=") {
                            cmdline_line.clone()
                        } else {
                            l.to_string()
                        }
                    })
                    .collect();
                lines.push(String::new());
                grub_default = lines.join("\n");
            } else {
                grub_default.push_str(&format!("{}\n", cmdline_line));
            }
        }
        tokio::fs::write(&default_path, grub_default)
            .await
            .map_err(|e| format!("/etc/default/grub yazılamadı: {}", e))?;

        // Chroot içinde grub2-install çalıştır (UEFI veya BIOS)
        let mut grub_args = vec!["grub2-install"];
        let efi_dir_arg;
        let disk_arg;

        if self.is_uefi {
            ui.log("GRUB UEFI modu kuruluyor (chroot içinde)…");
            efi_dir_arg = "--efi-directory=/boot/efi".to_string();
            grub_args.extend_from_slice(&[
                "--target=x86_64-efi",
                &efi_dir_arg,
                "--bootloader-id=PisiLinux",
                "--recheck",
            ]);
        } else {
            let install_dev = if self.boot_device.is_empty() { &self.disk } else { &self.boot_device };
            ui.log(format!("GRUB BIOS modu {} aygıtına kuruluyor (chroot içinde)…", install_dev));
            disk_arg = install_dev.clone();
            grub_args.extend_from_slice(&[
                "--target=i386-pc",
                &disk_arg,
                "--recheck",
            ]);
        }

        run_chroot(&self.mount, &grub_args).await?;

        ui.log("grub2-mkconfig çalıştırılıyor…");
        run_chroot(&self.mount, &[
            "grub2-mkconfig", "-o", "/boot/grub2/grub.cfg",
        ]).await?;

        Ok(())
    }
}

// ─────────────────────────────────────────────
// YARDIMCI: KOMUT ÇALIŞTIRICI
// ─────────────────────────────────────────────

static DEMO_MODE: AtomicBool = AtomicBool::new(false);

/// Disk adı ve bölüm numarasından tam aygıt yolunu oluşturur.
///
/// - SATA/SCSI: `/dev/sda`  + 1 → `/dev/sda1`
/// - NVMe:      `/dev/nvme0n1` + 1 → `/dev/nvme0n1p1`
/// - MMC:       `/dev/mmcblk0` + 1 → `/dev/mmcblk0p1`
fn part_path(disk: &str, num: u32) -> String {
    crate::installer::part_path(disk, num)
}


/// Başarısızlık durumunda stderr'i hata mesajı olarak döner.
async fn run_cmd(program: &str, args: &[&str]) -> Result<(), String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(3000)).await;
        return Ok(());
    }

    let output = Command::new(program)
        .args(args)
        .output()
        .await
        .map_err(|e| format!("{} çalıştırılamadı: {}", program, e))?;

    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
        Err(if stderr.is_empty() { stdout } else { stderr })
    }
}

/// `parted` komutlarını çalıştırmak için özel sarmalayıcı.
///
/// `parted -s` canlı sistemde kullanımda olan bir disk üzerinde bölüm
/// tablosunu kernel'e bildiremeyince exit 1 döndürür; ancak değişiklikler
/// diske **yazılmıştır** — kernel güncellemesi `sync_partition_table` ile
/// yapılır. Bu fonksiyon yalnızca gerçekten kritik hataları (disk bulunamadı
/// vb.) yakalar; "kernel haberdar edilemedi" uyarısını yok sayar.
async fn run_parted(args: &[&str]) -> Result<(), String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        return Ok(());
    }

    let output = Command::new("parted")
        .args(args)
        .output()
        .await
        .map_err(|e| format!("parted çalıştırılamadı: {}", e))?;

    if output.status.success() {
        return Ok(());
    }

    // Parted, kernel güncelleyemediğinde exit 1 verir ama disk yazımı
    // başarılıdır. Bu spesifik uyarıyı hata saymıyoruz.
    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let combined = format!("{}{}", stderr, stdout);

    let is_kernel_notify_warning = combined.contains("kernel değişiklikten haberdar edilemedi")
        || combined.contains("kernel was not notified")
        || combined.contains("unable to inform the kernel")
        || combined.contains("still in use");

    if is_kernel_notify_warning {
        // Uyarıyı yok say; sync_partition_table kernel'i güncelleyecek
        return Ok(());
    }

    // Gerçek bir hata
    Err(combined.trim().to_string())
}

/// Standart `chroot` ile verilen komutu hedef dizinde çalıştırır.
/// Bind-mount'ların önceden yapılmış olduğu varsayılır (MountBindJob).
async fn run_chroot(mount: &str, cmd: &[&str]) -> Result<(), String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
        return Ok(());
    }

    // chroot <mount> <cmd[0]> [cmd[1..]...]
    let mut args = vec![mount];
    args.extend_from_slice(cmd);

    let output = Command::new("chroot")
        .args(&args)
        .output()
        .await
        .map_err(|e| format!("chroot çalıştırılamadı: {}", e))?;

    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
        Err(if stderr.is_empty() { stdout } else { stderr })
    }
}

/// LUKS bölümünü güvenli şekilde formatlar.
///
/// Şifre, `sh -c` + string interpolasyon yerine doğrudan `stdin` pipe'ı
/// üzerinden iletilir; böylece shell injection saldırıları önlenir.
async fn run_luks_format(device: &str, password: &str) -> Result<(), String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
        return Ok(());
    }

    use tokio::io::AsyncWriteExt;

    let mut child = tokio::process::Command::new("cryptsetup")
        .args([
            "luksFormat", "-q", "--type", "luks2",
            "--key-file", "-",
            device,
        ])
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .map_err(|e| format!("cryptsetup luksFormat başlatılamadı: {}", e))?;

    if let Some(mut stdin) = child.stdin.take() {
        stdin.write_all(password.as_bytes()).await
            .map_err(|e| format!("cryptsetup stdin yazma hatası: {}", e))?;
    }

    let output = child.wait_with_output().await
        .map_err(|e| format!("cryptsetup bekleme hatası: {}", e))?;

    if output.status.success() {
        Ok(())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
    }
}

/// LUKS bölümünü güvenli şekilde açar (open).
///
/// Şifre, shell interpolasyon yerine `stdin` pipe'ı üzerinden iletilir.
async fn run_luks_open(device: &str, name: &str, password: &str) -> Result<(), String> {
    if DEMO_MODE.load(Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        return Ok(());
    }

    use tokio::io::AsyncWriteExt;

    let mut child = tokio::process::Command::new("cryptsetup")
        .args(["open", "--key-file", "-", device, name])
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .map_err(|e| format!("cryptsetup open başlatılamadı: {}", e))?;

    if let Some(mut stdin) = child.stdin.take() {
        stdin.write_all(password.as_bytes()).await
            .map_err(|e| format!("cryptsetup open stdin yazma hatası: {}", e))?;
    }

    let output = child.wait_with_output().await
        .map_err(|e| format!("cryptsetup open bekleme hatası: {}", e))?;

    if output.status.success() {
        Ok(())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
    }
}

/// Bölümlendirme öncesi diski hazırlar:
/// mevcut bağlamaları ve swap'ı ayırır, ardından kernel'e yeni
/// bölüm tablosunu duyurarak aygıt düğümlerinin hazır olmasını bekler.
async fn prepare_disk(disk: &str, ui: &UiSender) {
    if DEMO_MODE.load(Ordering::Relaxed) { return; }

    // 1. Diskteki aktif swap'ları kapat
    ui.log(format!("Swap ayrılıyor: {}…", disk));
    let lsblk = tokio::process::Command::new("lsblk")
        .args(["-o", "NAME,TYPE", "-l", "--noheadings", disk])
        .output().await;
    if let Ok(out) = lsblk {
        let text = String::from_utf8_lossy(&out.stdout);
        for line in text.lines() {
            if line.contains("part") {
                if let Some(name) = line.split_whitespace().next() {
                    let dev = format!("/dev/{}", name);
                    let _ = tokio::process::Command::new("swapoff")
                        .arg(&dev).status().await;
                }
            }
        }
    }

    // 2. Disk bölümlerini umount et (hata görmezden gelinir)
    ui.log(format!("Bağlantılar ayrılıyor: {}…", disk));
    for n in 1..=32u32 {
        let part = crate::installer::part_path(disk, n);
        if !std::path::Path::new(&part).exists() { break; }
        let _ = tokio::process::Command::new("umount")
            .args(["-lf", &part]).status().await;
    }

    // 3. İlk senkronizasyon
    let _ = tokio::process::Command::new("partprobe")
        .arg(disk).status().await;
    let _ = tokio::process::Command::new("udevadm")
        .args(["settle", "--timeout=10"]).status().await;
}

/// Bölümlendirme sonrası kernel'e yeni tabloyu bildirir ve aygıt
/// düğümlerinin hazır olmasını bekler. `parted`'ın uyarı vermesi
/// normaldir; bu fonksiyon hata döndürmez.
async fn sync_partition_table(disk: &str) {
    if DEMO_MODE.load(Ordering::Relaxed) { return; }
    let _ = tokio::process::Command::new("partprobe")
        .arg(disk).status().await;
    let _ = tokio::process::Command::new("blockdev")
        .args(["--rereadpt", disk]).status().await;
    let _ = tokio::process::Command::new("udevadm")
        .args(["settle", "--timeout=10"]).status().await;
    tokio::time::sleep(std::time::Duration::from_secs(2)).await;
}

/// ISO ortamında canlı sistemin kaynak dizinini otomatik tespit eder.
///
/// Sırasıyla:
///   1. `/proc/mounts` içinde squashfs/overlay mount noktalarını tara
///   2. Sabit aday listesini dene (her adayda bin/ + etc/ varlığı aranır)
///   3. Hiçbiri bulunamazsa `/` (canlı sistem kökü) kullan — rsync
///      zaten /proc /sys /dev /run'u exclude eder.
fn has_root_fs(path: &std::path::Path) -> bool {
    (path.join("bin").exists() || path.join("usr/bin").exists()) && path.join("etc").exists()
}

fn is_iso_mount(path: &str) -> bool {
    let sqfs_check = [
        "pisi/pisi.sqfs",
        "live/pisi.sqfs",
        "live/filesystem.squashfs",
        "casper/filesystem.squashfs",
        "live squashfs",
    ];
    for pattern in &sqfs_check {
        if std::path::Path::new(path).join(pattern).exists() {
            return true;
        }
    }
    false
}

pub fn detect_source_dir() -> String {
    // 1. /proc/mounts içinde squashfs veya overlay mount noktalarını tara
    if let Ok(mounts) = std::fs::read_to_string("/proc/mounts") {
        for line in mounts.lines() {
            let parts: Vec<&str> = line.split_whitespace().collect();
            if parts.len() < 3 { continue; }
            let fs_type   = parts[2];
            let mount_pt  = parts[1];

            match fs_type {
                "squashfs" => {
                    if mount_pt != "/" {
                        let p = std::path::Path::new(mount_pt);
                        if has_root_fs(p) {
                            return format!("{}/", mount_pt.trim_end_matches('/'));
                        }
                    }
                }
                "overlay" => {
                    // lowerdir parametresini ayıkla
                    let opts = parts.get(3).unwrap_or(&"");
                    for opt in opts.split(',') {
                        if let Some(lower) = opt.strip_prefix("lowerdir=") {
                            for dir in lower.split(':') {
                                let p = std::path::Path::new(dir);
                                if p.exists() && has_root_fs(p) {
                                    return format!("{}/", dir.trim_end_matches('/'));
                                }
                            }
                        }
                    }
                }
                _ => {}
            }
        }
    }

    // 2. Sabit aday listesi (her adayda bin/ + etc/ kontrolü)
    //    NOT: /bootmnt ISO kökü olabilir; altında pisi.sqfs varsa atlanır.
    for path in &[
        "/run/livecd/squashfs-root",
        "/run/live/rootfs",
        "/run/live/medium",
        "/lib/live/mount/rootfs",
        "/mnt/sqfs",
        "/mnt/livecd",
        "/mnt/cdrom",
        "/bootmnt/live",
        "/bootmnt/livecd",
    ] {
        let p = std::path::Path::new(path);
        if p.exists() && has_root_fs(p) {
            return format!("{}/", path);
        }
    }

    // 2b. /bootmnt son çare; ama ISO köküyse (pisi.sqfs varsa) atla
    let bootmnt = std::path::Path::new("/bootmnt");
    if bootmnt.exists() && !is_iso_mount("/bootmnt") && has_root_fs(bootmnt) {
        return "/bootmnt/".to_string();
    }

    // 3. Hiçbiri bulunamazsa canlı sistem kökü
    "/".to_string()
}

/// Kurulum kuyruğu için ortak yapılandırma.
pub struct JobQueueConfig {
    pub mount:               String,
    pub source:              String,
    pub locale:              String,
    pub timezone:            String,
    pub username:            String,
    pub password:            String,
    pub hostname:            String,
    pub kb_layout:           String,
    pub kb_variant:          String,
    pub demo_mode:           bool,
    pub boot_device:         String,
    pub bootloader_timeout:  u32,
    pub bootloader_password: String,
    pub kernel_options:      String,
    pub use_ntp:             bool,
    pub manual_year:         i32,
    pub manual_month:        u32,
    pub manual_day:          u32,
    pub manual_hour:         u32,
    pub manual_minute:       u32,
    pub root_password:       String,
    pub display_manager:     String,
    pub autologin:           bool,
    pub desktop_environment: String,
    pub selected_package_groups: Vec<String>,
}

// ─────────────────────────────────────────────
// FABRİKA: STANDART KURULUM KUYRUĞU
// ─────────────────────────────────────────────

// ─────────────────────────────────────────────
// PARTITION JOB  (otomatik bölümleme)
// ─────────────────────────────────────────────

use crate::installer::{PartitionPlan, PartitionTableType};

/// `parted` ve `mkfs` araçlarını kullanarak diski otomatik bölümlendirir.
/// Plan, PartitionStep'te hesaplanmış ve GlobalState'te saklanmıştır.
pub struct PartitionJob {
    pub plan: PartitionPlan,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let disk = &self.plan.disk;
        let is_gpt = self.plan.table_type == PartitionTableType::Gpt;

        // 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle
        prepare_disk(disk, ui).await;

        // 1. Bölüm tablosu oluştur
        let table_label = if is_gpt { "gpt" } else { "msdos" };
        ui.log(format!("parted {} mktable {}", disk, table_label));
        run_parted(&["-s", disk, "mktable", table_label]).await?;

        let mut next_mb: u64 = 1; // İlk MB'ı boot kayıtları için bırak

        // 2. EFI bölümü (yalnızca GPT/UEFI)
        if self.plan.efi_mb > 0 {
            let end_mb = next_mb + self.plan.efi_mb;
            ui.log(format!("EFI bölümü oluşturuluyor: {}–{} MB", next_mb, end_mb));
            run_parted(&[
                "-s", disk, "mkpart", "fat32",
                &format!("{}MB", next_mb), &format!("{}MB", end_mb),
            ]).await?;
            run_parted(&["-s", disk, "set", "1", "esp", "on"]).await?;
            next_mb = end_mb;
        }

        // 3. Swap bölümü
        let swap_end = next_mb + self.plan.swap_mb;
        let swap_num: u32 = if self.plan.efi_mb > 0 { 2 } else { 1 };
        ui.log(format!("Swap bölümü oluşturuluyor: {}–{} MB", next_mb, swap_end));
        run_parted(&[
            "-s", disk, "mkpart", "linux-swap",
            &format!("{}MB", next_mb), &format!("{}MB", swap_end),
        ]).await?;
        next_mb = swap_end;

        // 4. Kök bölümü (kalan alan)
        let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 };
        ui.log(format!("Kök bölümü oluşturuluyor: {}MB – disk sonu", next_mb));
        run_parted(&[
            "-s", disk, "mkpart", "ext4",
            &format!("{}MB", next_mb), "100%",
        ]).await?;

        // 4.5. Tüm mkpart bitti; kernel'e bildir ve aygıt düğümlerini bekle
        sync_partition_table(disk).await;

        // 5. Biçimlendirme
        let efi_part  = part_path(disk, 1);
        let swap_part = part_path(disk, swap_num);
        let root_part = part_path(disk, root_num);

        if self.plan.efi_mb > 0 {
            ui.log(format!("mkfs.fat -F32 {}", efi_part));
            run_cmd("mkfs.fat", &["-F32", &efi_part]).await?;
        }

        ui.log(format!("mkswap {}", swap_part));
        run_cmd("mkswap", &[&swap_part]).await?;

        ui.log(format!("swapon {}", swap_part));
        run_cmd("swapon", &[&swap_part]).await?;

        // LUKS şifreleme (kök bölümü)
        // Şifre, shell injection'ı önlemek için doğrudan stdin pipe üzerinden
        // iletilir; `sh -c` + string interpolasyon kullanılmaz.
        let root_device = if self.plan.encrypt_root && !self.plan.luks_password.is_empty() {
            let luks_name = "cryptroot";
            ui.log(format!("cryptsetup luksFormat {}…", root_part));
            run_luks_format(&root_part, &self.plan.luks_password).await
                .map_err(|e| format!("LUKS format hatası: {}", e))?;

            ui.log(format!("cryptsetup open {} {}…", root_part, luks_name));
            run_luks_open(&root_part, luks_name, &self.plan.luks_password).await
                .map_err(|e| format!("LUKS open hatası: {}", e))?;

            format!("/dev/mapper/{}", luks_name)
        } else {
            root_part.clone()
        };

        ui.log(format!("mkfs.ext4 -F {}", root_device));
        run_cmd("mkfs.ext4", &["-F", &root_device]).await?;

        Ok(())
    }
}

/// Bölüm yolunu bağla.
pub struct MountPartitionsJob {
    pub plan: PartitionPlan,
    pub mount: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let disk  = &self.plan.disk;
        let mount = &self.mount;
        let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 };
        let root_part = part_path(disk, root_num);

        let root_device = if self.plan.encrypt_root {
            "/dev/mapper/cryptroot".to_string()
        } else {
            root_part.clone()
        };

        ui.log(format!("mount {} {}", root_device, mount));
        run_cmd("mount", &[&root_device, mount]).await?;

        if self.plan.efi_mb > 0 {
            let efi_dir = format!("{}/boot/efi", mount);
            ui.log(format!("mkdir -p {}", efi_dir));
            tokio::fs::create_dir_all(&efi_dir).await
                .map_err(|e| format!("{} oluşturulamadı: {}", efi_dir, e))?;
            let efi_part = part_path(disk, 1);
            ui.log(format!("mount {} {}", efi_part, efi_dir));
            run_cmd("mount", &[&efi_part, &efi_dir]).await?;
        }

        Ok(())
    }
}

/// Tam kurulum iş kuyruğu.
///
/// Sıra:
///   1.  Disk bölümlendirme (parted + mkfs)
///   2.  Bölümleri bağlama
///   3.  /proc /sys /dev bind-mount (chroot hazırlığı)
///   4.  Sistem dosyalarını kopyalama (rsync)
///   5.  UUID'leri oku → fstab yaz
///   6.  Hostname + /etc/hosts
///   7.  Locale yapılandırması + locale-gen
///   8.  Zaman dilimi + hwclock
///   9.  Klavye (vconsole + X11)
///  10.  Kullanıcı oluşturma + şifre (chpasswd)
///  11.  initramfs yenile (mkinitramfs / dracut / mkinitcpio)
///  12.  GRUB kurulumu (chroot içinde)
///  13.  COMAR / pisi configure-pending
///  14.  ldconfig
///  15.  update-environment (PisiLinux init)
///  16.  Live ortam temizliği
///  17.  Bind-mount'ları temizle
pub fn build_full_job_queue(
    plan: PartitionPlan,
    cfg:  JobQueueConfig,
) -> JobQueue {
    DEMO_MODE.store(cfg.demo_mode, std::sync::atomic::Ordering::Relaxed);

    let is_uefi      = plan.efi_mb > 0;
    let encrypt_root = plan.encrypt_root;
    let disk         = plan.disk.clone();
    let mt           = cfg.mount.clone();
    let plan2        = plan.clone();
    let plan3        = plan.clone();

    // Kaynak dizini: parametre boşsa otomatik tespit et
    let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source };

    let mut jobs: Vec<Box<dyn Job>> = vec![
        // 1. Bölümlendir
        Box::new(PartitionJob { plan }),
        // 2. Bağla
        Box::new(MountPartitionsJob { plan: plan2, mount: mt.clone() }),
        // 3. /proc /sys /dev bind-mount
        Box::new(MountBindJob { mount: mt.clone() }),
        // 4. Kopyala
        Box::new(CopyFilesJob { source: src, target: mt.clone() }),
        // 5. UUID oku + fstab yaz
        Box::new(FstabFromBlkidJob { plan: plan3, mount: mt.clone() }),
        // 6. Hostname
        Box::new(ConfigureHostnameJob {
            mount:    mt.clone(),
            hostname: cfg.hostname.clone(),
        }),
        // 7. Locale
        Box::new(ConfigureLocaleJob {
            mount:    mt.clone(),
            locale:   cfg.locale.clone(),
        }),
        // 8. Zaman dilimi
        Box::new(ConfigureTimezoneJob {
            mount:    mt.clone(),
            timezone: cfg.timezone.clone(),
        }),
        // 9. Tarih/Saat (NTP)
        Box::new(ConfigureDateTimeJob {
            mount:  mt.clone(),
            use_ntp: cfg.use_ntp,
            year:   cfg.manual_year,
            month:  cfg.manual_month,
            day:    cfg.manual_day,
            hour:   cfg.manual_hour,
            minute: cfg.manual_minute,
        }),
        // 10. Klavye
        Box::new(ConfigureKeyboardJob {
            mount:   mt.clone(),
            layout:  cfg.kb_layout.clone(),
            variant: cfg.kb_variant.clone(),
        }),
        // 11. Kullanıcı
        Box::new(CreateUserJob {
            mount:         mt.clone(),
            username:      cfg.username.clone(),
            password:      cfg.password.clone(),
            root_password: cfg.root_password.clone(),
        }),
        // 12. initramfs
        Box::new(GenerateInitramfsJob { mount: mt.clone(), encrypt_root, lvm_active: false }),
    ];

    // 13. Bootloader (GRUB)
    jobs.push(Box::new(InstallGrubJob {
        disk: disk.clone(),
        mount: mt.clone(),
        is_uefi,
        boot_device: cfg.boot_device.clone(),
        timeout: cfg.bootloader_timeout,
        password: cfg.bootloader_password.clone(),
        kernel_options: cfg.kernel_options.clone(),
        encrypt_root,
    }));

    // 14. Ekran yöneticisi
    jobs.push(Box::new(ConfigureDisplayManagerJob {
        mount: mt.clone(),
        display_manager: cfg.display_manager.clone(),
        autologin: cfg.autologin,
        username: cfg.username.clone(),
        desktop_environment: cfg.desktop_environment.clone(),
    }));

    // 15. COMAR / pisi configure-pending
    jobs.push(Box::new(RunComarJob { mount: mt.clone() }));

    // 16. ldconfig
    jobs.push(Box::new(RunLdconfigJob { mount: mt.clone() }));

    // 17. update-environment (PisiLinux init yapılandırması)
    jobs.push(Box::new(UpdateEnvironmentJob { mount: mt.clone() }));

    // 18. Ek paket grupları (netinstall)
    if !cfg.selected_package_groups.is_empty() {
        jobs.push(Box::new(InstallExtraPackagesJob {
            mount: mt.clone(),
            package_groups: cfg.selected_package_groups.clone(),
        }));
    }

    // 19. Live ortam izlerini temizle
    jobs.push(Box::new(CleanupLiveJob { mount: mt.clone() }));

    // 20. Bind-mount temizle
    jobs.push(Box::new(UnmountBindJob { mount: mt.clone() }));

    JobQueue::new(jobs)
}

/// Ekran yöneticisi (SDDM/LightDM/GDM) yapılandırması.
pub struct ConfigureDisplayManagerJob {
    pub mount: String,
    pub display_manager: String,
    pub autologin: bool,
    pub username: String,
    pub desktop_environment: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let dm = &self.display_manager;
        ui.log(format!("Ekran yöneticisi yapılandırılıyor: {}", dm));

        match dm.as_str() {
            "sddm" => {
                let conf = format!("{}/usr/lib/sddm/sddm.conf.d/sddm.conf", self.mount);
                let session = match self.desktop_environment.as_str() {
                    "plasma" => "plasmax11.desktop",
                    "xfce" => "xfce.desktop",
                    "gnome" => "gnome.desktop",
                    "cinnamon" => "cinnamon.desktop",
                    "budgie" => "budgie-desktop.desktop",
                    "lxde" => "LXDE.desktop",
                    "lxqt" => "lxqt.desktop",
                    "mate" => "mate.desktop",
                    "lumina" => "lumina-desktop.desktop",
                    _ => "plasmax11.desktop",
                };
                let content = if self.autologin {
                    format!(
                        "[Autologin]\nUser={}\nSession={}\n\n[Theme]\nCurrent=pisilinux24\n",
                        self.username, session
                    )
                } else {
                    "[Autologin]\nUser=\nSession=\n\n[Theme]\nCurrent=pisilinux24\n".to_string()
                };
                tokio::fs::write(&conf, content)
                    .await
                    .map_err(|e| format!("sddm.conf yazılamadı: {}", e))?;
            }
            "lightdm" => {
                let conf = format!("{}/etc/lightdm/lightdm.conf", self.mount);
                // Mevcut dosyayı oku veya baştan yaz
                let mut content = String::new();
                if let Ok(existing) = tokio::fs::read_to_string(&conf).await {
                    content = existing;
                }
                if self.autologin {
                    let auto_lines = format!(
                        "[Seat:*]\nautologin-user={}\nautologin-session={}\n",
                        self.username, self.desktop_environment
                    );
                    content.push_str(&auto_lines);
                }
                tokio::fs::write(&conf, content)
                    .await
                    .map_err(|e| format!("lightdm.conf yazılamadı: {}", e))?;
            }
            "gdm" => {
                let dir = format!("{}/etc/gdm", self.mount);
                let _ = tokio::fs::create_dir_all(&dir).await;
                let conf = format!("{}/etc/gdm/custom.conf", self.mount);
                let session = match self.desktop_environment.as_str() {
                    "plasma" => "plasmax11.desktop",
                    "xfce" => "xfce.desktop",
                    "gnome" => "gnome.desktop",
                    "cinnamon" => "cinnamon.desktop",
                    "budgie" => "budgie-desktop.desktop",
                    "lxde" => "LXDE.desktop",
                    "lxqt" => "lxqt.desktop",
                    "mate" => "mate.desktop",
                    "lumina" => "lumina-desktop.desktop",
                    _ => "gnome.desktop",
                };
                let content = if self.autologin {
                    format!(
                        "[daemon]\nAutomaticLoginEnable=true\nAutomaticLogin={}\nDefaultSession={}\n",
                        self.username, session
                    )
                } else {
                    "[daemon]\n".to_string()
                };
                tokio::fs::write(&conf, content)
                    .await
                    .map_err(|e| format!("gdm/custom.conf yazılamadı: {}", e))?;
            }
            _ => {
                ui.log(format!("⚠ Bilinmeyen ekran yöneticisi: {}", dm));
            }
        }

        ui.log("✓ Ekran yöneticisi yapılandırması tamamlandı");
        Ok(())
    }
}

/// Ek paket gruplarını chroot içinde kurar.
pub struct InstallExtraPackagesJob {
    pub mount: String,
    pub package_groups: Vec<String>,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        use crate::steps::netinstall::PACKAGE_GROUPS;

        let mut packages: Vec<&str> = Vec::new();
        for (key, _, pkgs) in PACKAGE_GROUPS {
            if self.package_groups.contains(&key.to_string()) {
                packages.extend_from_slice(pkgs);
            }
        }

        if packages.is_empty() {
            ui.log("Ek paket seçilmedi, atlanıyor.");
            return Ok(());
        }

        let pkg_list = packages.join(" ");
        ui.log(format!("Ek paketler kuruluyor: {}", pkg_list));
        match run_chroot(&self.mount, &["pisi", "install", &pkg_list]).await {
            Ok(_) => ui.log("✓ Ek paketler kuruldu"),
            Err(e) => {
                ui.log(format!("⚠ pisi install uyarısı: {}", e));
                ui.log("⚠ Bu hata kritik değilse kurulum devam edecek.");
            }
        }
        Ok(())
    }
}

/// COMAR servislerini ve tetikleyicilerini yapılandırır.
pub struct RunComarJob {
    pub mount: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // pisi configure-pending: paket post-install scriptlerini çalıştırır.
        // Yapılandırılacak paket yoksa çıkış kodu sıfır dışı olabilir — fatal değil.
        ui.log("pisi configure-pending çalıştırılıyor (COMAR)…");
        match run_chroot(&self.mount, &["pisi", "configure-pending"]).await {
            Ok(_) => {
                ui.log("✓ pisi configure-pending tamamlandı");
            }
            Err(e) => {
                // "no packages to configure" gibi durumlar hata değil
                if e.contains("no packages") || e.contains("Nothing to do") {
                    ui.log("⚠ configure-pending: yapılandırılacak paket yok, atlanıyor");
                } else {
                    // Gerçek hata — uyar ama kurulumu durdurma
                    ui.log(format!("⚠ pisi configure-pending uyarısı: {}", e));
                    ui.log("⚠ Bu uyarı kritik değilse kurulum devam edecek.");
                }
            }
        }
        Ok(())
    }
}

/// ldconfig: paylaşımlı kütüphane önbelleğini yeniden oluşturur.
pub struct RunLdconfigJob {
    pub mount: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        ui.log("ldconfig çalıştırılıyor…");
        match run_chroot(&self.mount, &["ldconfig"]).await {
            Ok(_) => ui.log("✓ ldconfig tamamlandı"),
            Err(e) => ui.log(format!("⚠ ldconfig uyarısı: {}", e)),
        }
        Ok(())
    }
}

/// update-environment: PisiLinux init/betiklerini yeniden yapılandırır.
pub struct UpdateEnvironmentJob {
    pub mount: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        ui.log("update-environment çalıştırılıyor…");
        match run_chroot(&self.mount, &["update-environment"]).await {
            Ok(_) => ui.log("✓ update-environment tamamlandı"),
            Err(e) => ui.log(format!("⚠ update-environment uyarısı: {}", e)),
        }
        Ok(())
    }
}

/// Live ortam izlerini hedef sistemden temizler.
pub struct CleanupLiveJob {
    pub mount: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let mt = &self.mount;
        ui.log("Live ortam izleri temizleniyor…");

        // run/livemedia
        let _ = tokio::fs::remove_dir_all(format!("{}/run/livemedia", mt)).await;
        // var/cache/pisi/packages
        let _ = tokio::fs::remove_dir_all(format!("{}/var/cache/pisi/packages", mt)).await;
        // YALI desktop dosyaları
        //let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali.desktop", mt)).await;
        //let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali-bin.desktop", mt)).await;
        let _ = tokio::fs::remove_dir_all(format!("{}/bootmnt", mt)).await;
        //pisi kullanıcısını sil (ev diziniyle birlikte), YALI paketlerini kaldır
        let _ = run_chroot(&self.mount, &["userdel", "-r", "pisi"]).await;
        let _ = run_chroot(&self.mount, &["pisi", "rm", "yali-rs"]).await;
        // live paket deposunu kaldır
        let _ = run_chroot(&self.mount, &["pisi", "rr", "live"]).await;
        // depolarları yeniden ekle (stable2 + contrib)
        ui.log("Depolar Ekleniyor…");
        let _ = run_chroot(&self.mount, &["pisi", "ar", "stable2", "https://stable2.pisilinux.org/pisi-index.xml.xz"]).await;
        let _ = run_chroot(&self.mount, &["pisi", "ar", "contrib", "https://contrib.pisilinux.org/pisi-index.xml.xz"]).await;
        // depoları aktif et
        ui.log("Depolar aktif ediliyor...");
        let _ = run_chroot(&self.mount, &["pisi", "er", "contrib", "stable2"]).await;
        // paket listelerini güncelle
        ui.log("Depolar Güncelleniyor...");
        let _ = run_chroot(&self.mount, &["pisi", "ur"]).await;

        ui.log("✓ Live temizlik tamamlandı");
        Ok(())
    }
}

/// blkid ile UUID okuyup fstab'ı doğrudan yazan job.
/// (async closure Rust'ta trait object olarak kullanılamaz;
///  bu nedenle ayrı bir Job struct'ı gerekli.)
struct FstabFromBlkidJob {
    plan:  PartitionPlan,
    mount: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        ui.log("blkid ile UUID'ler okunuyor…");
        let (root_uuid, efi_uuid, swap_uuid) = read_partition_uuids(&self.plan).await?;
        ui.log(format!("Kök UUID: {}", root_uuid));

        let job = GenerateFstabJob {
            mount: self.mount.clone(),
            root_uuid,
            efi_uuid,
            swap_uuid,
            encrypt_root: self.plan.encrypt_root,
        };
        job.run(ui).await
    }
}

// ─────────────────────────────────────────────
// CHROOT YAPILANDIRMA JOB'LARI
// ─────────────────────────────────────────────

/// /etc/hostname ve /etc/hosts dosyalarını yazar.
pub struct ConfigureHostnameJob {
    pub mount:    String,
    pub hostname: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let etc_dir = format!("{}/etc", self.mount);
        let _ = tokio::fs::create_dir_all(&etc_dir).await;

        // /etc/hostname
        let hostname_path = format!("{}/etc/hostname", self.mount);
        ui.log(format!("Yazılıyor: {}", hostname_path));
        tokio::fs::write(&hostname_path, format!("{}\n", self.hostname))
            .await
            .map_err(|e| format!("/etc/hostname yazılamadı: {}", e))?;

        // /etc/hosts
        let hosts_path = format!("{}/etc/hosts", self.mount);
        let hosts_content = format!(
            "127.0.0.1\tlocalhost\n\
             ::1\t\tlocalhost\n\
             127.0.1.1\t{hostname}\n",
            hostname = self.hostname
        );
        ui.log(format!("Yazılıyor: {}", hosts_path));
        tokio::fs::write(&hosts_path, hosts_content)
            .await
            .map_err(|e| format!("/etc/hosts yazılamadı: {}", e))?;

        Ok(())
    }
}

/// /etc/locale.conf ve /etc/locale.gen'i yazar, sonra locale-gen çalıştırır.
pub struct ConfigureLocaleJob {
    pub mount:    String,
    pub locale:   String,  // ör. "tr_TR.UTF-8"
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // Locale değeri doğrulama: yalnızca alfanumerik, '.', '_', '-', '@'
        // karakterlerine izin ver. Yol manipülasyonu ve injection önlenir.
        let locale_valid = !self.locale.is_empty()
            && !self.locale.contains("..")
            && !self.locale.contains('/')
            && !self.locale.contains('\n')
            && self.locale.chars().all(|c| c.is_alphanumeric() || "._-@".contains(c));
        if !locale_valid {
            return Err(format!("Geçersiz locale değeri: '{}'", self.locale));
        }

        let etc_dir = format!("{}/etc", self.mount);
        let _ = tokio::fs::create_dir_all(&etc_dir).await;
        let _ = tokio::fs::create_dir_all(format!("{}/etc/env.d", self.mount)).await;

        // /etc/locale.conf  (systemd-style)
        let conf_path = format!("{}/etc/locale.conf", self.mount);
        let conf = format!(
            "LANG={locale}\nLC_ALL={locale}\n",
            locale = self.locale
        );
        ui.log(format!("Yazılıyor: {}", conf_path));
        let _ = tokio::fs::write(&conf_path, conf).await;

        // /etc/env.d/03locale  (PisiLinux native)
        let pisi_locale_path = format!("{}/etc/env.d/03locale", self.mount);
        let pisi_locale = format!(
            "LANG={locale}\nLC_ALL={locale}\n",
            locale = self.locale
        );
        ui.log(format!("Yazılıyor: {}", pisi_locale_path));
        let _ = tokio::fs::write(&pisi_locale_path, pisi_locale).await;

        // /etc/locale.gen — satırın başındaki # kaldırılır
        let gen_path = format!("{}/etc/locale-gen", self.mount);
        ui.log(format!("Güncelleniyor: {}", gen_path));
        let content = tokio::fs::read_to_string(&gen_path).await.unwrap_or_default();
        let updated = content
            .lines()
            .map(|line| {
                let stripped = line.trim_start_matches('#').trim_start();
                if stripped.starts_with(&self.locale) {
                    stripped.to_string() // yorumu kaldır
                } else {
                    line.to_string()
                }
            })
            .collect::<Vec<_>>()
            .join("\n");
        tokio::fs::write(&gen_path, updated + "\n")
            .await
            .map_err(|e| format!("/etc/locale-gen yazılamadı: {}", e))?;

        // locale-gen çalıştır — kurulum sonrası locale desteği için zorunlu
        ui.log("locale-gen çalıştırılıyor…");
        run_chroot(&self.mount, &["locale-gen"]).await?;

        Ok(())
    }
}

/// Zaman dilimini ayarlar ve hwclock'u senkronize eder.
pub struct ConfigureTimezoneJob {
    pub mount:    String,
    pub timezone: String, // ör. "Europe/Istanbul"
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // Path traversal koruması: timezone değeri yalnızca alfanumerik,
        // '/', '_', '-', '+' karakterlerine izin verir; ".." asla geçemez.
        let tz = &self.timezone;
        let tz_valid = !tz.is_empty()
            && !tz.contains("..")
            && tz.chars().all(|c| c.is_alphanumeric() || "/._-+".contains(c));
        if !tz_valid {
            return Err(format!("Geçersiz timezone değeri: '{}'", tz));
        }

        // Sembolik link: /etc/localtime → /usr/share/zoneinfo/<tz>
        let link_target = format!("/usr/share/zoneinfo/{}", self.timezone);
        let link_path   = format!("{}/etc/localtime", self.mount);

        ui.log(format!("ln -sf {} {}", link_target, link_path));
        // Önce varsa eski linki sil
        let _ = tokio::fs::remove_file(&link_path).await;
        // tokio'da Unix symlink için spawn_blocking kullan
        let lt = link_target.clone();
        let lp = link_path.clone();
        tokio::task::spawn_blocking(move || {
            std::os::unix::fs::symlink(&lt, &lp)
        }).await
            .map_err(|e| format!("symlink spawn hatası: {}", e))?
            .map_err(|e| format!("localtime symlink: {}", e))?;

        // hwclock --systohc
        ui.log("hwclock --systohc çalıştırılıyor…");
        // Hata kritik değil, uyar ve devam et
        match run_chroot(&self.mount, &["hwclock", "--systohc"]).await {
            Ok(_) => ui.log("hwclock senkronize edildi."),
            Err(e) => ui.log(format!("⚠ hwclock uyarısı (kritik değil): {}", e)),
        }

        Ok(())
    }
}

/// NTP ve manuel tarih/saat yapılandırması.
pub struct ConfigureDateTimeJob {
    pub mount:         String,
    pub use_ntp:       bool,
    pub year:          i32,
    pub month:         u32,
    pub day:           u32,
    pub hour:          u32,
    pub minute:        u32,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        if self.use_ntp {
            ui.log("NTP etkinleştiriliyor…");
            // systemd-timesyncd veya ntpd'yi etkinleştir
            let _ = run_chroot(&self.mount, &["systemctl", "enable", "systemd-timesyncd"]).await;
            let _ = run_chroot(&self.mount, &["systemctl", "start", "systemd-timesyncd"]).await;
        } else {
            ui.log("Manuel tarih/saat ayarlanıyor…");
            let date_str = format!(
                "{:04}-{:02}-{:02} {:02}:{:02}:00",
                self.year, self.month, self.day, self.hour, self.minute
            );
            let _ = run_chroot(&self.mount, &["date", "-s", &date_str]).await;
            let _ = run_chroot(&self.mount, &["hwclock", "--systohc"]).await;
        }
        Ok(())
    }
}

/// Klavye düzenini /etc/vconsole.conf ve X11 yapılandırmasına yazar.
pub struct ConfigureKeyboardJob {
    pub mount:    String,
    pub layout:   String,  // ör. "tr"
    pub variant:  String,  // ör. "f" veya ""
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // /etc/vconsole.conf  (systemd-style TTY)
        let vconsole_path = format!("{}/etc/vconsole.conf", self.mount);
        let keymap = if self.variant.is_empty() {
            self.layout.clone()
        } else {
            format!("{}-{}", self.layout, self.variant)
        };
        let vconsole = format!("KEYMAP={}\nFONT=ter-v16n\n", keymap);
        ui.log(format!("Yazılıyor: {}", vconsole_path));
        let _ = tokio::fs::write(&vconsole_path, vconsole).await;

        // /etc/conf.d/mudur  (PisiLinux native init)
        let mudur_path = format!("{}/etc/conf.d/mudur", self.mount);
        let mudur_conf = format!("keymap={}\nfont=ter-v16n\n", keymap);
        ui.log(format!("Yazılıyor: {}", mudur_path));
        let _ = tokio::fs::write(&mudur_path, mudur_conf).await;

        // /etc/X11/xorg.conf.d/00-keyboard.conf  (X11 için)
        let x11_dir  = format!("{}/etc/X11/xorg.conf.d", self.mount);
        let x11_path = format!("{}/00-keyboard.conf", x11_dir);
        tokio::fs::create_dir_all(&x11_dir).await
            .map_err(|e| format!("{} oluşturulamadı: {}", x11_dir, e))?;

        let options_line = if self.variant.is_empty() {
            String::new()
        } else {
            format!("\n\t\tOption \"XkbVariant\" \"{}\"", self.variant)
        };

        let x11_conf = format!(
            "Section \"InputClass\"\n\
             \tIdentifier \"system-keyboard\"\n\
             \tMatchIsKeyboard \"on\"\n\
             \tOption \"XkbLayout\" \"{layout}\"{options}\n\
             EndSection\n",
            layout  = self.layout,
            options = options_line,
        );
        ui.log(format!("Yazılıyor: {}", x11_path));
        tokio::fs::write(&x11_path, x11_conf)
            .await
            .map_err(|e| format!("xorg keyboard conf yazılamadı: {}", e))?;

        Ok(())
    }
}

/// Kullanıcı hesabı oluşturur ve şifresini ayarlar.
pub struct CreateUserJob {
    pub mount:         String,
    pub username:      String,
    pub password:      String,
    pub root_password: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // wheel grubunun varlığını garantile (PisiLinux'ta olmayabilir)
        let group_file = format!("{}/etc/group", self.mount);
        let group_content = tokio::fs::read_to_string(&group_file)
            .await.unwrap_or_default();
        if !group_content.lines().any(|l| l.starts_with("wheel:")) {
            ui.log("wheel grubu oluşturuluyor…");
            run_chroot(&self.mount, &["groupadd", "-r", "wheel"]).await
                .unwrap_or_default(); // hata olsa bile devam et
        }

        // useradd: kullanıcıyı temel gruplarla oluştur
        // PisiLinux'ta "storage" grubu yoksa useradd hata verir; -M flag ile grub yoksa atla
        // Güvenli yöntem: önce her grubu tek tek ekle
        let groups = ["users", "pnp", "disk", "audio", "video", "power", "dialout", "lp", "lpadmin", "cdrom", "floppy"];
        for grp in &groups {
            if group_content.lines().any(|l| l.starts_with(&format!("{}:", grp))) {
                // grup zaten var
            } else {
                // grup yoksa oluşturma (hata görmezden gel)
                let _ = run_chroot(&self.mount, &["groupadd", grp]).await;
            }
        }

        // Kullanıcıyı oluştur
        let groups_str = groups.join(",");
        ui.log(format!("useradd -m -G {} -s /bin/bash {}", groups_str, self.username));
        run_chroot(&self.mount, &[
            "useradd",
            "-m",
            "-G", &groups_str,
            "-s", "/bin/bash",
            &self.username,
        ]).await?;

        // // chpasswd ile şifre ayarla (stdin üzerinden güvenli)
        // ui.log(format!("Şifre ayarlanıyor: {}", self.username));
        // let chpasswd_input = format!("{}:{}", self.username, self.password);
        // set_password_via_chpasswd(&self.mount, &chpasswd_input).await?;

        // // Root şifresi: belirlenmişse ayarla, yoksa kilitle
        // if self.root_password.is_empty() {
        //     ui.log("Root hesabı kilitleniyor…");
        //     run_chroot(&self.mount, &["passwd", "-l", "root"]).await?;
        // } else {
        //     ui.log("Root şifresi ayarlanıyor…");
        //     let root_input = format!("root:{}", self.root_password);
        //     set_password_via_chpasswd(&self.mount, &root_input).await?;
        // }

        // 2. chpasswd ile şifre ayarla (YENİ BASİTLEŞTİRİLMİŞ VE KESİN YÖNTEM)
        ui.log(format!("Şifre ayarlanıyor: {}", self.username));
        // sh -c kullanarak echo ile chpasswd'e şifreyi güvenli bir şekilde üflüyoruz
        let user_cmd = format!("echo '{}:{}' | chpasswd -c SHA512", self.username, self.password);
        run_chroot(&self.mount, &["sh", "-c", &user_cmd]).await?;

        // 3. Root şifresi: belirlenmişse ayarla, yoksa kilitle
        if self.root_password.is_empty() {
            ui.log("Root hesabı kilitleniyor…");
            run_chroot(&self.mount, &["passwd", "-l", "root"]).await?;
        } else {
            ui.log("Root şifresi ayarlanıyor…");
            let root_cmd = format!("echo 'root:{}' | chpasswd -c SHA512", self.root_password);
            run_chroot(&self.mount, &["sh", "-c", &root_cmd]).await?;
}


        // sudoers: wheel grubu sudo yetkisi alsın
        let sudoers_dir = format!("{}/etc/sudoers.d", self.mount);
        tokio::fs::create_dir_all(&sudoers_dir).await
            .map_err(|e| format!("{} oluşturulamadı: {}", sudoers_dir, e))?;
        let sudoers_path = format!("{}/wheel", sudoers_dir);
        ui.log(format!("Yazılıyor: {}", sudoers_path));
        tokio::fs::write(&sudoers_path, "%wheel ALL=(ALL:ALL) ALL\n")
            .await
            .map_err(|e| format!("sudoers yazılamadı: {}", e))?;

        // Dosya izinleri: sudoers 440 olmalı
        run_cmd("chmod", &["440", &sudoers_path]).await?;

        Ok(())
    }
}

// /// `chpasswd` komutuna stdin üzerinden kullanıcı:şifre gönderir.
// async fn set_password_via_chpasswd(mount: &str, input: &str) -> Result<(), String> {
//     if DEMO_MODE.load(std::sync::atomic::Ordering::Relaxed) {
//         tokio::time::sleep(std::time::Duration::from_millis(2500)).await;
//         return Ok(());
//     }

//     use tokio::io::AsyncWriteExt;
//     use tokio::process::Command;

//     let mut child = Command::new("chroot")
//         .args([mount, "chpasswd"])
//         .stdin(std::process::Stdio::piped())
//         .stdout(std::process::Stdio::null())
//         .stderr(std::process::Stdio::piped())
//         .spawn()
//         .map_err(|e| format!("chpasswd başlatılamadı: {}", e))?;

//     if let Some(mut stdin) = child.stdin.take() {
//         let mut data = input.as_bytes().to_vec();
//         if !data.ends_with(b"\n") {
//             data.push(b'\n');
//         }
//         stdin.write_all(&data).await
//             .map_err(|e| format!("chpasswd stdin yazma hatası: {}", e))?;
//     }

//     let output = child.wait_with_output().await
//         .map_err(|e| format!("chpasswd bekleme hatası: {}", e))?;

//     if output.status.success() {
//         Ok(())
//     } else {
//         Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
//     }
// }


/// fstab'ı doğrudan dosyaya yazar (genfstab yerine; ISO ortamında daha güvenilir).
pub struct GenerateFstabJob {
    pub mount:     String,
    pub root_uuid: String, // blkid ile alınır
    pub efi_uuid:  Option<String>,
    pub swap_uuid: String,
    pub encrypt_root: bool,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let etc_dir = format!("{}/etc", self.mount);
        let _ = tokio::fs::create_dir_all(&etc_dir).await;

        let fstab_path = format!("{}/etc/fstab", self.mount);
        ui.log(format!("Yazılıyor: {}", fstab_path));

        let mut fstab = String::from(
            "# /etc/fstab — Yali tarafından otomatik oluşturuldu\n\
             # <device>  <mountpoint>  <fs>    <options>         <dump> <pass>\n\n"
        );

        // Kök bölümü
        if self.encrypt_root {
            fstab.push_str(
                "/dev/mapper/cryptroot  /       ext4  defaults,noatime  0  1\n"
            );
        } else {
            fstab.push_str(&format!(
                "UUID={}  /       ext4  defaults,noatime  0  1\n",
                self.root_uuid
            ));
        }

        // EFI bölümü
        if let Some(ref efi) = self.efi_uuid {
            fstab.push_str(&format!(
                "UUID={}  /boot/efi  vfat  umask=0077        0  2\n",
                efi
            ));
        }

        // Swap
        fstab.push_str(&format!(
            "UUID={}  none    swap  sw                0  0\n",
            self.swap_uuid
        ));

        tokio::fs::write(&fstab_path, fstab)
            .await
            .map_err(|e| format!("fstab yazılamadı: {}", e))?;

        // crypttab (LUKS için)
        if self.encrypt_root {
            let crypttab_path = format!("{}/etc/crypttab", self.mount);
            let crypttab = format!(
                "cryptroot UUID={}  none  luks\n",
                self.root_uuid
            );
            ui.log(format!("Yazılıyor: {}", crypttab_path));
            tokio::fs::write(&crypttab_path, crypttab)
                .await
                .map_err(|e| format!("crypttab yazılamadı: {}", e))?;
        }

        Ok(())
    }
}

/// Disk bölümlerinin UUID'lerini `blkid` ile okur.
pub async fn read_partition_uuids(
    plan: &PartitionPlan,
) -> Result<(String, Option<String>, String), String> {
    let (root_num, swap_num): (u32, u32) = if plan.efi_mb > 0 { (3, 2) } else { (2, 1) };

    let root_part = part_path(&plan.disk, root_num);
    let swap_part = part_path(&plan.disk, swap_num);

    // Şifreli kök: blkid'den LUKS UUID'sini al (crypttab için)
    let root_uuid = blkid_uuid(&root_part).await?;
    let swap_uuid = blkid_uuid(&swap_part).await?;
    let efi_uuid  = if plan.efi_mb > 0 {
        Some(blkid_uuid(&part_path(&plan.disk, 1)).await?)
    } else {
        None
    };

    Ok((root_uuid, efi_uuid, swap_uuid))
}

async fn blkid_uuid(device: &str) -> Result<String, String> {
    if DEMO_MODE.load(std::sync::atomic::Ordering::Relaxed) {
        tokio::time::sleep(std::time::Duration::from_millis(1500)).await;
        return Ok(format!("demo-uuid-{}", device.replace("/", "-")));
    }

    let output = tokio::process::Command::new("blkid")
        .args(["-s", "UUID", "-o", "value", device])
        .output()
        .await
        .map_err(|e| format!("blkid çalıştırılamadı: {}", e))?;

    let uuid = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if uuid.is_empty() {
        Err(format!("{} için UUID bulunamadı", device))
    } else {
        Ok(uuid)
    }
}

// ─────────────────────────────────────────────
// MANUEL BÖLÜMLEME İÇİN DESTEKLEYİCİ JOB'LAR
// ─────────────────────────────────────────────

use crate::installer::{CustomPartition, FsType};

/// Manuel bölümleme ayarlarını disk üzerinde uygulayan ve biçimlendiren Job.
pub struct CustomPartitionJob {
    pub disk: String,
    pub custom_partitions: Vec<CustomPartition>,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let is_uefi = std::path::Path::new("/sys/firmware/efi").exists();

        // 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle
        prepare_disk(&self.disk, ui).await;

        let is_gpt = is_uefi; // Genellikle UEFI ise GPT kullanılır
        // MBR'de "primary" zorunludur, GPT'de ise bu alan isim (label) alanıdır.
        let part_type_or_name = if is_gpt { "" } else { "primary" };

        // 1. Bölüm tablosu oluştur
        let table_label = if is_uefi { "gpt" } else { "msdos" };
        ui.log(format!("parted {} mktable {}", self.disk, table_label));
        run_parted(&["-s", &self.disk, "mktable", table_label]).await?;

        let mut next_mb: u64 = 1; // İlk MB boot sektörü

        // 2. Bölümleri parted ile oluştur
        for (i, part) in self.custom_partitions.iter().enumerate() {
            let part_num = (i + 1) as u32;
            let end_str = if part.size_mb == 0 {
                "100%".to_string()
            } else {
                format!("{}MB", next_mb + part.size_mb)
            };
            let start_str = format!("{}MB", next_mb);

            let fs_label = match part.fstype {
                FsType::Fat32 => "fat32",
                FsType::Swap  => "linux-swap",
                FsType::Ext4  => "ext4",
                FsType::Ext3  => "ext3",
                FsType::Btrfs => "btrfs",
                FsType::Xfs   => "xfs",
                FsType::Ntfs  => "ntfs",
                // LVM PV için parted "ext4" ile işaretle, pvcreate sonra
                FsType::Lvm   => "ext4",
                // LVM LV doğrudan parted ile oluşturulmaz
                FsType::LvmLv => continue,
            };

            ui.log(format!(
                "Bölüm {} oluşturuluyor: {} - {} ({})",
                part_num, start_str, end_str, fs_label
            ));
            run_parted(&[
                "-s", &self.disk, "mkpart", part_type_or_name, fs_label,
                &start_str, &end_str
            ]).await?;

            if part.mountpoint == "/boot/efi" || part.fstype == FsType::Fat32 {
                run_parted(&["-s", &self.disk, "set", &part_num.to_string(), "esp", "on"]).await?;
            }

            if part.size_mb > 0 {
                next_mb += part.size_mb;
            }
        }

        // 2.5. Tüm mkpart bitti; kernel'e bildir ve aygıt düğümlerini bekle
        sync_partition_table(&self.disk).await;

        // 3. Biçimlendir (LUKS ile şifreleme desteklenir)
        for part in &self.custom_partitions {
            let path = &part.device;

            // LUKS şifreleme (kök veya isteğe bağlı bölümler)
            // Şifre, shell injection'ı önlemek için doğrudan stdin pipe üzerinden
            // iletilir; `sh -c` + string interpolasyon kullanılmaz.
            let fs_device = if part.encrypt && !part.luks_password.is_empty() {
                let luks_name = if part.luks_name.is_empty() {
                    format!("crypt_{}", part.mountpoint.replace("/", "_"))
                } else {
                    part.luks_name.clone()
                };
                let luks_name = luks_name.trim_start_matches('_').to_string();

                ui.log(format!("cryptsetup luksFormat {}…", path));
                run_luks_format(path, &part.luks_password).await
                    .map_err(|e| format!("LUKS format hatası ({}): {}", path, e))?;

                ui.log(format!("cryptsetup open {} {}…", path, luks_name));
                run_luks_open(path, &luks_name, &part.luks_password).await
                    .map_err(|e| format!("LUKS open hatası ({}): {}", path, e))?;

                format!("/dev/mapper/{}", luks_name)
            } else {
                path.clone()
            };

            match part.fstype {
                FsType::Fat32 => {
                    ui.log(format!("mkfs.fat -F32 {}", fs_device));
                    run_cmd("mkfs.fat", &["-F32", &fs_device]).await?;
                }
                FsType::Swap => {
                    ui.log(format!("mkswap {}", path));
                    run_cmd("mkswap", &[path]).await?;
                    ui.log(format!("swapon {}", path));
                    run_cmd("swapon", &[path]).await?;
                }
                FsType::Ext4 => {
                    ui.log(format!("mkfs.ext4 -F {}", fs_device));
                    run_cmd("mkfs.ext4", &["-F", &fs_device]).await?;
                }
                FsType::Ext3 => {
                    ui.log(format!("mkfs.ext3 -F {}", fs_device));
                    run_cmd("mkfs.ext3", &["-F", &fs_device]).await?;
                }
                FsType::Btrfs => {
                    ui.log(format!("mkfs.btrfs -f {}", fs_device));
                    run_cmd("mkfs.btrfs", &["-f", &fs_device]).await?;
                }
                FsType::Xfs => {
                    ui.log(format!("mkfs.xfs -f {}", fs_device));
                    run_cmd("mkfs.xfs", &["-f", &fs_device]).await?;
                }
                FsType::Ntfs => {
                    ui.log(format!("mkfs.ntfs -f {}", fs_device));
                    run_cmd("mkfs.ntfs", &["-f", &fs_device]).await?;
                }
                FsType::Lvm => {
                    ui.log(format!("pvcreate {}", path));
                    run_cmd("pvcreate", &[path]).await?;
                }
                // LV ayrı bir job'da oluşturulur
                FsType::LvmLv => {}
            }
        }

        Ok(())
    }
}

/// LVM Volume Group ve Logical Volume'leri oluşturur.
pub struct SetupLvmJob {
    pub volume_groups: Vec<crate::installer::VolumeGroup>,
    pub logical_volumes: Vec<crate::installer::LogicalVolume>,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        for vg in &self.volume_groups {
            let pv_str = vg.pv_devices.join(" ");
            ui.log(format!("vgcreate {} {}", vg.name, pv_str));
            run_cmd("vgcreate", &[&vg.name, &pv_str]).await?;
        }

        for lv in &self.logical_volumes {
            let size_str = if lv.size_mb == 0 {
                "100%FREE".to_string()
            } else {
                format!("{}M", lv.size_mb)
            };
            let lv_path = format!("/dev/{}/{}", lv.vg_name, lv.name);
            ui.log(format!("lvcreate -L {} -n {} {}", size_str, lv.name, lv.vg_name));
            run_cmd("lvcreate", &["-L", &size_str, "-n", &lv.name, &lv.vg_name]).await?;

            let fs_cmd = match lv.fstype {
                crate::installer::FsType::Ext4 => "mkfs.ext4",
                crate::installer::FsType::Ext3 => "mkfs.ext3",
                crate::installer::FsType::Btrfs => "mkfs.btrfs",
                crate::installer::FsType::Xfs => "mkfs.xfs",
                _ => continue,
            };
            ui.log(format!("{} {}", fs_cmd, lv_path));
            run_cmd(fs_cmd, &[&lv_path]).await?;
        }
        Ok(())
    }
}

/// Manuel bölümleri ve mantıksal hacimleri kök dizinden başlayarak hiyerarşik olarak hedef yola bağlayan Job.
pub struct MountCustomPartitionsJob {
    pub custom_partitions: Vec<CustomPartition>,
    pub logical_volumes: Vec<crate::installer::LogicalVolume>,
    pub mount: String,
}

impl MountCustomPartitionsJob {
    /// Şifreli bölümler için mapper yolunu döndürür.
    fn device_path(&self, part: &CustomPartition) -> String {
        if part.encrypt {
            let luks_name = if part.luks_name.is_empty() {
                format!("crypt_{}", part.mountpoint.replace("/", "_").trim_start_matches('_'))
            } else {
                part.luks_name.clone()
            };
            format!("/dev/mapper/{}", luks_name)
        } else {
            part.device.clone()
        }
    }
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        // 1. Kök (/) bölümünü (fiziksel partition veya LVM LV) bul ve bağla
        let root_device = if let Some(root_part) = self.custom_partitions.iter().find(|p| p.mountpoint == "/") {
            self.device_path(root_part)
        } else if let Some(root_lv) = self.logical_volumes.iter().find(|lv| lv.mountpoint == "/") {
            format!("/dev/{}/{}", root_lv.vg_name, root_lv.name)
        } else {
            return Err("Kök (/) bölümü manuel listede veya mantıksal bölümlerde bulunamadı.".to_string());
        };

        ui.log(format!("Kök dizin bağlanıyor: {} ➔ {}", root_device, self.mount));
        run_cmd("mount", &[&root_device, &self.mount]).await?;

        // 2. Diğer bölümleri ve mantıksal hacimleri topla, sırala ve bağla (derinliğe göre artan sırada)
        struct MountItem {
            device: String,
            mountpoint: String,
        }

        let mut mount_items = Vec::new();

        for part in &self.custom_partitions {
            if part.mountpoint != "/" && part.fstype != FsType::Swap && !part.mountpoint.is_empty() {
                mount_items.push(MountItem {
                    device: self.device_path(part),
                    mountpoint: part.mountpoint.clone(),
                });
            }
        }

        for lv in &self.logical_volumes {
            if lv.mountpoint != "/" && lv.fstype != FsType::Swap && !lv.mountpoint.is_empty() {
                mount_items.push(MountItem {
                    device: format!("/dev/{}/{}", lv.vg_name, lv.name),
                    mountpoint: lv.mountpoint.clone(),
                });
            }
        }

        mount_items.sort_by_key(|item| item.mountpoint.len());

        for item in mount_items {
            let target_path = format!("{}{}", self.mount, item.mountpoint);
            ui.log(format!("Bağlama noktası dizini oluşturuluyor: {}", target_path));
            tokio::fs::create_dir_all(&target_path).await
                .map_err(|e| format!("{} oluşturulamadı: {}", target_path, e))?;
            ui.log(format!("Bağlanıyor: {} ➔ {}", item.device, target_path));
            run_cmd("mount", &[&item.device, &target_path]).await?;
        }

        Ok(())
    }
}

/// Manuel bölümleme planına göre fstab dosyası üreten Job.
pub struct FstabFromCustomPartitionsJob {
    pub custom_partitions: Vec<CustomPartition>,
    pub logical_volumes: Vec<crate::installer::LogicalVolume>,
    pub mount: String,
}

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

    async fn run(&self, ui: &UiSender) -> Result<(), String> {
        let etc_dir = format!("{}/etc", self.mount);
        let _ = tokio::fs::create_dir_all(&etc_dir).await;

        let fstab_path = format!("{}/etc/fstab", self.mount);
        ui.log(format!("Yazılıyor: {}", fstab_path));

        let mut fstab = String::from(
            "# /etc/fstab — Yali tarafından otomatik (manuel mod) oluşturuldu\n\
             # <device>  <mountpoint>  <fs>    <options>         <dump> <pass>\n\n"
        );

        let mut crypttab = String::from(
            "# /etc/crypttab — Yali tarafından otomatik oluşturuldu\n"
        );

        for part in &self.custom_partitions {
            if part.mountpoint.is_empty() { continue; }

            let is_encrypted = part.encrypt && !part.luks_password.is_empty();

            let device_field = if is_encrypted {
                let luks_uuid = blkid_uuid(&part.device).await?;
                let luks_name = if part.luks_name.is_empty() {
                    format!("crypt_{}", part.mountpoint.replace("/", "_").trim_start_matches('_'))
                } else {
                    part.luks_name.clone()
                };
                crypttab.push_str(&format!(
                    "{} UUID={}  none  luks\n",
                    luks_name, luks_uuid
                ));
                format!("/dev/mapper/{:<30}", luks_name)
            } else {
                ui.log(format!("{} için UUID okunuyor…", part.device));
                let uuid = blkid_uuid(&part.device).await?;
                format!("UUID={:<36}", uuid)
            };

            let (mp, options, dump, pass) = match part.fstype {
                FsType::Swap => ("none", "sw", "0", "0"),
                FsType::Fat32 => ("/boot/efi", "umask=0077", "0", "2"),
                FsType::Ext4 | FsType::Ext3 if part.mountpoint == "/" => ("/", "defaults,noatime", "0", "1"),
                _ => {
                    let pass_val = if part.mountpoint == "/" { "1" } else { "2" };
                    (part.mountpoint.as_str(), "defaults,noatime", "0", pass_val)
                }
            };

            let fs_name = match part.fstype {
                FsType::Ext4 => "ext4",
                FsType::Ext3 => "ext3",
                FsType::Btrfs => "btrfs",
                FsType::Xfs => "xfs",
                FsType::Fat32 => "vfat",
                FsType::Ntfs => "ntfs-3g",
                FsType::Swap => "swap",
                FsType::Lvm | FsType::LvmLv => "ext4",
            };

            fstab.push_str(&format!(
                "{}  {:<12}  {:<6}  {:<18}  {}  {}\n",
                device_field, mp, fs_name, options, dump, pass
            ));
        }

        // LVM mantıksal bölümlerini fstab'a ekle
        for lv in &self.logical_volumes {
            if lv.mountpoint.is_empty() { continue; }

            let device_field = format!("/dev/{}/{}", lv.vg_name, lv.name);

            let (mp, options, dump, pass) = match lv.fstype {
                FsType::Swap => ("none", "sw", "0", "0"),
                FsType::Fat32 => ("/boot/efi", "umask=0077", "0", "2"),
                FsType::Ext4 | FsType::Ext3 if lv.mountpoint == "/" => ("/", "defaults,noatime", "0", "1"),
                _ => {
                    let pass_val = if lv.mountpoint == "/" { "1" } else { "2" };
                    (lv.mountpoint.as_str(), "defaults,noatime", "0", pass_val)
                }
            };

            let fs_name = match lv.fstype {
                FsType::Ext4 => "ext4",
                FsType::Ext3 => "ext3",
                FsType::Btrfs => "btrfs",
                FsType::Xfs => "xfs",
                FsType::Fat32 => "vfat",
                FsType::Ntfs => "ntfs-3g",
                FsType::Swap => "swap",
                _ => "ext4",
            };

            fstab.push_str(&format!(
                "{:<42}  {:<12}  {:<6}  {:<18}  {}  {}\n",
                device_field, mp, fs_name, options, dump, pass
            ));
        }

        tokio::fs::write(&fstab_path, fstab)
            .await
            .map_err(|e| format!("fstab yazılamadı: {}", e))?;

        // crypttab (sadece şifreli bölümler için)
        let crypttab_device_count = crypttab.lines().count().saturating_sub(1); // header satırını sayma
        if crypttab_device_count > 0 {
            let crypttab_path = format!("{}/etc/crypttab", self.mount);
            ui.log(format!("Yazılıyor: {}", crypttab_path));
            let _ = tokio::fs::write(&crypttab_path, crypttab).await;
        }

        Ok(())
    }
}

/// Manuel bölümleme kullanıldığında derlenecek kurulum iş kuyruğu.
pub fn build_custom_job_queue(
    disk:              String,
    custom_partitions: Vec<CustomPartition>,
    volume_groups:     Vec<crate::installer::VolumeGroup>,
    logical_volumes:   Vec<crate::installer::LogicalVolume>,
    cfg:               JobQueueConfig,
) -> JobQueue {
    DEMO_MODE.store(cfg.demo_mode, std::sync::atomic::Ordering::Relaxed);

    let is_uefi      = std::path::Path::new("/sys/firmware/efi").exists();
    let encrypt_root = custom_partitions.iter().any(|p| p.mountpoint == "/" && p.encrypt);
    let mt           = cfg.mount.clone();

    let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source };

    let mut jobs: Vec<Box<dyn Job>> = vec![
        Box::new(CustomPartitionJob { disk: disk.clone(), custom_partitions: custom_partitions.clone() }),
        Box::new(SetupLvmJob { volume_groups: volume_groups.clone(), logical_volumes: logical_volumes.clone() }),
        Box::new(MountCustomPartitionsJob { custom_partitions: custom_partitions.clone(), logical_volumes: logical_volumes.clone(), mount: mt.clone() }),
        Box::new(MountBindJob { mount: mt.clone() }),
        Box::new(CopyFilesJob { source: src, target: mt.clone() }),
        Box::new(FstabFromCustomPartitionsJob { custom_partitions: custom_partitions.clone(), logical_volumes: logical_volumes.clone(), mount: mt.clone() }),
        Box::new(ConfigureHostnameJob { mount: mt.clone(), hostname: cfg.hostname.clone() }),
        Box::new(ConfigureLocaleJob { mount: mt.clone(), locale: cfg.locale.clone() }),
        Box::new(ConfigureTimezoneJob { mount: mt.clone(), timezone: cfg.timezone.clone() }),
        Box::new(ConfigureDateTimeJob {
            mount:  mt.clone(),
            use_ntp: cfg.use_ntp,
            year:   cfg.manual_year,
            month:  cfg.manual_month,
            day:    cfg.manual_day,
            hour:   cfg.manual_hour,
            minute: cfg.manual_minute,
        }),
        Box::new(ConfigureKeyboardJob { mount: mt.clone(), layout: cfg.kb_layout.clone(), variant: cfg.kb_variant.clone() }),
        Box::new(CreateUserJob { mount: mt.clone(), username: cfg.username.clone(), password: cfg.password.clone(), root_password: cfg.root_password.clone() }),
        Box::new(GenerateInitramfsJob { mount: mt.clone(), encrypt_root, lvm_active: !volume_groups.is_empty() }),
    ];

    jobs.push(Box::new(InstallGrubJob {
        disk: disk.clone(),
        mount: mt.clone(),
        is_uefi,
        boot_device: cfg.boot_device.clone(),
        timeout: cfg.bootloader_timeout,
        password: cfg.bootloader_password.clone(),
        kernel_options: cfg.kernel_options.clone(),
        encrypt_root,
    }));

    jobs.push(Box::new(ConfigureDisplayManagerJob {
        mount: mt.clone(),
        display_manager: cfg.display_manager.clone(),
        autologin: cfg.autologin,
        username: cfg.username.clone(),
        desktop_environment: cfg.desktop_environment.clone(),
    }));
    jobs.push(Box::new(RunComarJob { mount: mt.clone() }));
    jobs.push(Box::new(RunLdconfigJob { mount: mt.clone() }));
    jobs.push(Box::new(UpdateEnvironmentJob { mount: mt.clone() }));
    if !cfg.selected_package_groups.is_empty() {
        jobs.push(Box::new(InstallExtraPackagesJob {
            mount: mt.clone(),
            package_groups: cfg.selected_package_groups.clone(),
        }));
    }
    jobs.push(Box::new(CleanupLiveJob { mount: mt.clone() }));
    jobs.push(Box::new(UnmountBindJob { mount: mt.clone() }));

    JobQueue::new(jobs)
}
