use mount_fstab::fstype::FsType;
use mount_fstab::options::Options;
use mount_fstab::spec::Spec;
use mount_fstab::types::{Entry, Fstab, MountPoint};
use mount_fstab::validate::Severity;

fn make_entry(spec_str: &str, mount: &str, fstype_str: &str, opts: &str, passno: u32) -> Entry {
    let mut e = Entry::new(
        Spec::parse(spec_str).unwrap(),
        MountPoint::new(mount).unwrap(),
        FsType::parse(fstype_str).unwrap(),
        Options::parse(opts).unwrap(),
    );
    e.passno = passno;
    e
}

#[test]
fn valid_fstab_no_warnings() {
    let fstab = Fstab {
        entries: vec![
            make_entry("UUID=root", "/", "ext4", "defaults", 1),
            make_entry("UUID=boot", "/boot", "ext4", "defaults", 2),
        ],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(diags.is_empty(), "expected no diagnostics, got: {diags:?}");
}

#[test]
fn root_must_have_passno_1() {
    let fstab = Fstab {
        entries: vec![make_entry("UUID=root", "/", "ext4", "defaults", 0)],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(diags.iter().any(|d| d.severity == Severity::Error
        && d.message.contains("root")
        && d.message.contains("passno")));
}

#[test]
fn duplicate_mount_point_warning() {
    let fstab = Fstab {
        entries: vec![
            make_entry("UUID=a", "/mnt", "ext4", "defaults", 0),
            make_entry("UUID=b", "/mnt", "ext4", "defaults", 0),
        ],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(
        diags
            .iter()
            .any(|d| d.severity == Severity::Warning && d.message.contains("duplicate"))
    );
}

#[test]
fn swap_must_have_none_mountpoint() {
    let fstab = Fstab {
        entries: vec![Entry::new(
            Spec::parse("/swap.img").unwrap(),
            MountPoint::new("/mnt").unwrap(), // wrong! should be "none"
            FsType::swap(),
            Options::parse("sw").unwrap(),
        )],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(
        diags
            .iter()
            .any(|d| d.severity == Severity::Error && d.message.contains("swap"))
    );
}

#[test]
fn warning_for_ignore_fstype() {
    let fstab = Fstab {
        entries: vec![make_entry("UUID=a", "/mnt", "ignore", "defaults", 0)],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(diags.iter().any(|d| d.message.contains("ignore")));
}

#[test]
fn error_when_no_root_and_no_passno_1_entry() {
    let fstab = Fstab {
        entries: vec![
            make_entry("UUID=root", "/", "ext4", "defaults", 0),
            make_entry("UUID=data", "/data", "ext4", "defaults", 0),
        ],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(diags.iter().any(|d| d.severity == Severity::Error));
}

#[test]
fn error_when_multiple_passno_1_roots() {
    let fstab = Fstab {
        entries: vec![
            make_entry("UUID=a", "/", "ext4", "defaults", 1),
            make_entry("UUID=b", "/", "ext4", "defaults", 1),
        ],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(
        diags
            .iter()
            .any(|d| d.severity == Severity::Error && d.message.contains("multiple"))
    );
}

#[test]
fn no_warning_for_swap_with_correct_mountpoint() {
    let fstab = Fstab {
        entries: vec![Entry::new(
            Spec::parse("/swap.img").unwrap(),
            MountPoint::swap(),
            FsType::swap(),
            Options::parse("sw").unwrap(),
        )],
        ..Default::default()
    };
    let diags = fstab.validate();
    // Should NOT have swap-related errors
    assert!(!diags.iter().any(|d| d.message.contains("swap")));
}

#[test]
fn warn_for_user_with_exec_override() {
    let fstab = Fstab {
        entries: vec![make_entry("UUID=a", "/mnt", "ext4", "user,exec", 0)],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(
        diags
            .iter()
            .any(|d| d.message.contains("user") && d.message.contains("exec"))
    );
}

#[test]
fn warn_for_owner_with_exec_override() {
    let fstab = Fstab {
        entries: vec![make_entry("UUID=a", "/mnt", "ext4", "owner,exec", 0)],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(
        diags
            .iter()
            .any(|d| d.message.contains("owner") && d.message.contains("exec"))
    );
}

#[test]
fn no_warning_for_user_without_exec() {
    let fstab = Fstab {
        entries: vec![make_entry("UUID=a", "/mnt", "ext4", "user", 0)],
        ..Default::default()
    };
    let diags = fstab.validate();
    // Should not have exec override warnings
    assert!(!diags.iter().any(|d| d.message.contains("exec")));
}

#[test]
fn duplicate_mountpoint_with_different_devices() {
    let fstab = Fstab {
        entries: vec![
            make_entry("UUID=a", "/mnt", "ext4", "defaults", 0),
            make_entry("UUID=b", "/mnt", "xfs", "defaults", 0),
        ],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(
        diags
            .iter()
            .any(|d| d.severity == Severity::Warning && d.message.contains("duplicate"))
    );
}

#[test]
fn valid_fstab_with_nfs_and_local() {
    let fstab = Fstab {
        entries: vec![
            make_entry("UUID=root", "/", "ext4", "defaults", 1),
            make_entry("UUID=boot", "/boot", "ext4", "defaults", 2),
            make_entry("UUID=data", "/data", "ext4", "defaults", 0),
            make_entry("server:/exports", "/mnt/nfs", "nfs", "rw,hard", 0),
        ],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(diags.is_empty(), "expected no diagnostics, got: {diags:?}");
}

#[test]
fn no_warning_for_group_with_exec() {
    // group does NOT imply noexec, so group,exec should not warn
    let fstab = Fstab {
        entries: vec![make_entry("UUID=a", "/mnt", "ext4", "group,exec", 0)],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(
        !diags
            .iter()
            .any(|d| d.message.contains("exec") && d.message.contains("group"))
    );
}

#[test]
fn warning_for_id_tag() {
    let fstab = Fstab {
        entries: vec![make_entry("ID=ata-XXX", "/mnt", "ext4", "defaults", 0)],
        ..Default::default()
    };
    let diags = fstab.validate();
    assert!(diags.iter().any(|d| d.message.contains("ID=")));
}
