use mount_fstab::*;
use std::path::PathBuf;

/// Real-world fstab example from a Linux system.
const REAL_WORLD_FSTAB: &str = "\
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
/dev/disk/by-uuid/cbc17347-e82e-41b7-8e8f-3bb5ed125b4b / ext4 defaults 0 1
/dev/disk/by-uuid/C556-89E5 /boot/efi vfat defaults 0 1
/swap.img none swap sw 0 0
";

#[test]
fn parse_real_world_fstab() {
    let fstab = Fstab::parse_str(REAL_WORLD_FSTAB).unwrap();
    assert_eq!(fstab.entries.len(), 3);

    // First entry: root
    let root = &fstab.entries[0];
    assert!(root.file.is_root());
    assert_eq!(root.vfstype.as_str(), "ext4");
    assert_eq!(root.passno, 1);
    assert!(root.options.has("defaults"));

    // Second entry: boot
    let boot = &fstab.entries[1];
    assert_eq!(boot.file.as_path(), PathBuf::from("/boot/efi"));
    assert_eq!(boot.vfstype.as_str(), "vfat");

    // Third entry: swap
    let swap = &fstab.entries[2];
    assert!(swap.is_swap());
    assert_eq!(swap.spec, Spec::Device(PathBuf::from("/swap.img")));
    assert!(swap.file.is_swap());
}

#[test]
fn roundtrip_real_world_fstab() {
    let fstab = Fstab::parse_str(REAL_WORLD_FSTAB).unwrap();
    let output = fstab.to_string();
    let re_parsed = Fstab::parse_str(&output).unwrap();

    assert_eq!(re_parsed.entries.len(), fstab.entries.len());
    for (a, b) in fstab.entries.iter().zip(re_parsed.entries.iter()) {
        assert_eq!(a.spec, b.spec, "spec mismatch");
        assert_eq!(a.file, b.file, "mountpoint mismatch");
        assert_eq!(a.vfstype, b.vfstype, "fstype mismatch");
        assert_eq!(a.options, b.options, "options mismatch");
        assert_eq!(a.freq, b.freq, "freq mismatch");
        assert_eq!(a.passno, b.passno, "passno mismatch");
    }
}

#[test]
fn parse_and_edit() {
    let mut fstab = Fstab::parse_str("UUID=root / ext4 defaults 0 1\n").unwrap();
    assert_eq!(fstab.entries.len(), 1);

    // Add a new entry
    let boot = Entry::new(
        Spec::parse("UUID=boot").unwrap(),
        MountPoint::new("/boot").unwrap(),
        FsType::parse("ext4").unwrap(),
        Options::parse("defaults,noatime").unwrap(),
    );
    fstab.add(boot);
    assert_eq!(fstab.entries.len(), 2);

    // Remove the first entry
    let removed = fstab.remove(0);
    assert!(removed.is_some());
    assert_eq!(fstab.entries.len(), 1);
    assert_eq!(fstab.entries[0].file.as_path(), PathBuf::from("/boot"));
}

#[test]
fn parse_nfs_with_options() {
    let input = "server.example.com:/exports /mnt/nfs nfs rw,hard,intr 0 0\n";
    let fstab = Fstab::parse_str(input).unwrap();
    let e = &fstab.entries[0];
    assert_eq!(
        e.spec,
        Spec::NetworkMount {
            host: "server.example.com".into(),
            path: PathBuf::from("/exports"),
        }
    );
    assert!(e.options.has("rw"));
    assert!(e.options.has("hard"));
    assert!(e.options.has("intr"));
}

#[test]
fn spec_display_roundtrip() {
    let cases = vec![
        ("/dev/sda1", Spec::Device(PathBuf::from("/dev/sda1"))),
        ("LABEL=Boot", Spec::Label("Boot".into())),
        ("UUID=abc-123", Spec::Uuid("abc-123".into())),
        ("PARTLABEL=System", Spec::PartLabel("System".into())),
        ("PARTUUID=xyz", Spec::PartUuid("xyz".into())),
        ("proc", Spec::Keyword("proc".into())),
        (
            "host:/path",
            Spec::NetworkMount {
                host: "host".into(),
                path: PathBuf::from("/path"),
            },
        ),
    ];

    for (expected_str, spec) in cases {
        let entry = Entry::new(
            spec,
            MountPoint::new("/mnt").unwrap(),
            FsType::parse("ext4").unwrap(),
            Options::parse("defaults").unwrap(),
        );
        let fstab = Fstab {
            entries: vec![entry],
            ..Default::default()
        };
        let output = fstab.to_string();
        assert!(
            output.starts_with(expected_str),
            "expected output to start with '{expected_str}', got '{output}'"
        );
    }
}

#[test]
fn roundtrip_preserves_comment_positions() {
    let input = "\
# /etc/fstab

# Root
UUID=root / ext4 defaults 0 1

# Data
UUID=data /data ext4 defaults 0 0

# End
";
    let fstab = Fstab::parse_str(input).unwrap();
    let output = fstab.to_string();
    // Re-parse and check comment positions match
    let re_parsed = Fstab::parse_str(&output).unwrap();
    assert_eq!(re_parsed.intro_comment, fstab.intro_comment);
    assert_eq!(re_parsed.trailing_comment, fstab.trailing_comment);
    for (a, b) in fstab.entries.iter().zip(re_parsed.entries.iter()) {
        assert_eq!(a.comment, b.comment);
    }
}

#[test]
fn debian_style_fstab_roundtrip() {
    let input = "\
# /etc/fstab: static file system information.
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
UUID=abc123 /               ext4    errors=remount-ro 0       1
UUID=def456 /boot           ext4    defaults          0       2
UUID=ghi789 /home           ext4    defaults          0       2
UUID=jkl012 none            swap    sw                0       0
";
    let fstab = Fstab::parse_str(input).unwrap();
    assert_eq!(fstab.entries.len(), 4);
    let output = fstab.to_string();
    let re_parsed = Fstab::parse_str(&output).unwrap();
    assert_eq!(re_parsed.entries.len(), 4);
    // All specs match
    for (a, b) in fstab.entries.iter().zip(re_parsed.entries.iter()) {
        assert_eq!(a.spec, b.spec);
        assert_eq!(a.vfstype, b.vfstype);
    }
}

#[test]
fn arch_style_fstab_roundtrip() {
    let input = "\
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
UUID=rootfs	/         	ext4  	rw,relatime	0 1
UUID=bootfs	/boot     	vfat  	rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro	0 2
";
    let fstab = Fstab::parse_str(input).unwrap();
    assert_eq!(fstab.entries.len(), 2);
    let output = fstab.to_string();
    let re_parsed = Fstab::parse_str(&output).unwrap();
    assert_eq!(re_parsed.entries.len(), 2);
}

#[test]
fn rhel_style_fstab_roundtrip() {
    let input = "\
#
# /etc/fstab
# Created by anaconda
#
/dev/mapper/rhel-root   /                       xfs     defaults        0 0
UUID=abc                /boot                   xfs     defaults        0 0
/dev/mapper/rhel-swap   none                    swap    defaults        0 0
";
    let fstab = Fstab::parse_str(input).unwrap();
    assert_eq!(fstab.entries.len(), 3);
    let output = fstab.to_string();
    let re_parsed = Fstab::parse_str(&output).unwrap();
    assert_eq!(re_parsed.entries.len(), 3);
}

#[test]
fn edit_preserves_other_entries() {
    let mut fstab = Fstab::parse_str(
        "\
UUID=a / ext4 defaults 0 1
UUID=b /boot ext4 defaults 0 2
UUID=c /home ext4 defaults 0 2
",
    )
    .unwrap();

    // Replace middle entry
    let new_boot = Entry::new(
        Spec::parse("UUID=newboot").unwrap(),
        MountPoint::new("/boot").unwrap(),
        FsType::parse("xfs").unwrap(),
        Options::parse("defaults,noatime").unwrap(),
    );
    fstab.replace(1, new_boot);

    assert_eq!(fstab.entries.len(), 3);
    assert_eq!(fstab.entries[0].spec, Spec::Uuid("a".into()));
    assert_eq!(fstab.entries[1].spec, Spec::Uuid("newboot".into()));
    assert_eq!(fstab.entries[2].spec, Spec::Uuid("c".into()));
}

#[test]
fn write_atomic_creates_file() {
    use std::fs;
    let tmp = tempfile::tempdir().unwrap();
    let path = tmp.path().join("fstab");

    let fstab = Fstab::parse_str("UUID=root / ext4 defaults 0 1\n").unwrap();
    fstab.write_atomic(&path).unwrap();

    let content = fs::read_to_string(&path).unwrap();
    assert!(content.contains("UUID=root"));
}

#[test]
fn empty_fstab_roundtrip() {
    let fstab = Fstab::new();
    let output = fstab.to_string();
    assert_eq!(output, "");
    let re_parsed = Fstab::parse_str(&output).unwrap();
    assert!(re_parsed.entries.is_empty());
}
