use std::sync::atomic::Ordering;
use tokio::process::Command;

use crate::installer::{CustomPartition, FsType, PartitionPlan, PartitionTableType};
use super::{blkid_uuid, part_path, run_cmd, DEMO_MODE, Job, UiSender};

// ─────────────────────────────────────────────
// YARDIMCI: DISK / BÖLÜMLEME KOMUTLARI
// ─────────────────────────────────────────────

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

/// 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;
}

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

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

// ─────────────────────────────────────────────
// FSTAB JOB'LARI
// ─────────────────────────────────────────────

/// 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.)
pub struct FstabFromBlkidJob {
    pub plan:  PartitionPlan,
    pub 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
    }
}

/// 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.
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))
}

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

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

            if part.skip_format {
                ui.log(format!("⏭️ {} korunuyor (biçimlendirme atlandı)", fs_device));
                continue;
            }

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