use mount_fstab::options::{MountPermission, OptItem, OptionClass, Options};

#[test]
fn parse_simple_flag() {
    let opts = Options::parse("defaults").unwrap();
    assert!(opts.has("defaults"));
}

#[test]
fn parse_multiple_flags() {
    let opts = Options::parse("rw,noatime,nofail").unwrap();
    assert!(opts.has("rw"));
    assert!(opts.has("noatime"));
    assert!(opts.has("nofail"));
}

#[test]
fn parse_key_value() {
    let opts = Options::parse("size=10G,mode=755").unwrap();
    assert_eq!(opts.get("size"), Some("10G"));
    assert_eq!(opts.get("mode"), Some("755"));
}

#[test]
fn last_option_wins() {
    let opts = Options::parse("ro,rw").unwrap();
    assert!(!opts.is_readonly());
    let opts2 = Options::parse("rw,ro").unwrap();
    assert!(opts2.is_readonly());
}

#[test]
fn quoted_value_preserves_comma() {
    let opts = Options::parse(r#"context="system_u:object_r:tmp_t:s0:c127,c456",noatime"#).unwrap();
    assert_eq!(
        opts.get("context"),
        Some("system_u:object_r:tmp_t:s0:c127,c456")
    );
    assert!(opts.has("noatime"));
}

#[test]
fn single_quoted_value() {
    let opts = Options::parse("key='value,with,commas'").unwrap();
    assert_eq!(opts.get("key"), Some("value,with,commas"));
}

#[test]
fn parse_empty_options() {
    let opts = Options::parse("").unwrap();
    assert!(opts.is_empty());
}

#[test]
fn serialize_simple() {
    let opts = Options::parse("rw,noatime").unwrap();
    assert_eq!(opts.to_string(), "rw,noatime");
}

#[test]
fn serialize_with_value() {
    let opts = Options::parse("size=10G,mode=755").unwrap();
    assert_eq!(opts.to_string(), "size=10G,mode=755");
}

#[test]
fn serialize_roundtrip() {
    let inputs = [
        "defaults",
        "rw,noatime,nofail",
        "size=10G,mode=755",
        "ro,nosuid,nodev",
    ];
    for input in inputs {
        let opts = Options::parse(input).unwrap();
        assert_eq!(opts.to_string(), input, "roundtrip failed for: {input}");
    }
}

#[test]
fn set_adds_option() {
    let mut opts = Options::parse("rw").unwrap();
    opts.set("noatime", None);
    assert!(opts.has("noatime"));
}

#[test]
fn remove_option() {
    let mut opts = Options::parse("rw,noatime,nofail").unwrap();
    opts.remove("noatime");
    assert!(!opts.has("noatime"));
    assert!(opts.has("rw"));
    assert!(opts.has("nofail"));
}

#[test]
fn append_option() {
    let mut opts = Options::parse("rw").unwrap();
    opts.append(OptItem::flag("noatime").unwrap());
    assert_eq!(opts.to_string(), "rw,noatime");
}

#[test]
fn is_readonly() {
    assert!(Options::parse("ro").unwrap().is_readonly());
    assert!(!Options::parse("rw").unwrap().is_readonly());
}

#[test]
fn has_nofail() {
    assert!(Options::parse("nofail").unwrap().has_nofail());
    assert!(!Options::parse("defaults").unwrap().has_nofail());
}

#[test]
fn is_netdev() {
    assert!(Options::parse("_netdev").unwrap().is_netdev());
    assert!(!Options::parse("defaults").unwrap().is_netdev());
}

#[test]
fn option_classification() {
    let opts = Options::parse("ro,noexec").unwrap();
    for item in opts.vfs_options() {
        assert_eq!(item.class(), Some(OptionClass::Vfs));
    }
    let opts = Options::parse("sync").unwrap();
    for item in opts.fs_options() {
        assert_eq!(item.class(), Some(OptionClass::Filesystem));
    }
    let opts = Options::parse("nofail,_netdev").unwrap();
    for item in opts.user_options() {
        assert_eq!(item.class(), Some(OptionClass::Userspace));
    }
}

#[test]
fn unknown_option_is_none_class() {
    let item = OptItem::flag("mycustomopt").unwrap();
    assert_eq!(item.class(), None);
}

#[test]
fn optitem_empty_name_is_error() {
    assert!(OptItem::flag("").is_err());
}

#[test]
fn iter_preserves_order() {
    let opts = Options::parse("a,b,c,d").unwrap();
    let names: Vec<&str> = opts.iter().map(|i| i.name()).collect();
    assert_eq!(names, vec!["a", "b", "c", "d"]);
}

#[test]
fn defaults_constructor() {
    let opts = Options::defaults();
    assert!(opts.has("defaults"));
}

#[test]
fn parse_x_prefix_comment_option() {
    let opts = Options::parse("defaults,x-systemd.automount").unwrap();
    assert!(opts.has("x-systemd.automount"));
}

#[test]
#[allow(non_snake_case)]
fn parse_X_prefix_comment_option() {
    // X- options are NOT stored (per libmount), but we parse them
    let opts = Options::parse("defaults,X-mount.mkdir").unwrap();
    assert!(opts.has("X-mount.mkdir"));
}

#[test]
fn parse_option_with_empty_value() {
    let opts = Options::parse("key=").unwrap();
    assert_eq!(opts.get("key"), Some(""));
}

#[test]
fn parse_multiple_defaults() {
    // "defaults" may appear multiple times, treated as no-ops
    let opts = Options::parse("defaults,defaults,defaults").unwrap();
    assert!(opts.has("defaults"));
}

#[test]
fn defaults_is_not_readonly() {
    let opts = Options::parse("defaults").unwrap();
    assert!(!opts.is_readonly()); // defaults → rw
}

#[test]
fn ro_overrides_defaults() {
    let opts = Options::parse("defaults,ro").unwrap();
    assert!(opts.is_readonly());
}

#[test]
fn user_implies_nosuid_nodev_noexec_but_not_enforced() {
    // The library documents this but doesn't modify the option list
    let opts = Options::parse("user").unwrap();
    assert!(opts.has("user"));
    assert!(!opts.has("nosuid")); // user implies nosuid but doesn't add it
    assert!(!opts.has("nodev"));
    assert!(!opts.has("noexec"));
}

#[test]
fn user_then_exec_overrides_implicit_noexec() {
    let opts = Options::parse("user,exec").unwrap();
    assert!(opts.has("user"));
    assert!(opts.has("exec"));
    // mount(8): user implies noexec, but exec overrides it
}

#[test]
fn mount_permission_none_by_default() {
    assert_eq!(
        Options::parse("defaults").unwrap().mount_permission(),
        MountPermission::None
    );
}

#[test]
fn mount_permission_user() {
    assert_eq!(
        Options::parse("user").unwrap().mount_permission(),
        MountPermission::User
    );
}

#[test]
fn mount_permission_users() {
    assert_eq!(
        Options::parse("users").unwrap().mount_permission(),
        MountPermission::Users
    );
}

#[test]
fn mount_permission_last_wins() {
    assert_eq!(
        Options::parse("user,users").unwrap().mount_permission(),
        MountPermission::Users
    );
    assert_eq!(
        Options::parse("users,user").unwrap().mount_permission(),
        MountPermission::User
    );
}

#[test]
fn option_classification_x_prefix() {
    let item = OptItem::flag("X-mount.mkdir").unwrap();
    assert_eq!(item.class(), Some(OptionClass::Userspace));
}

#[test]
fn option_classification_x_lowercase_prefix() {
    let item = OptItem::flag("x-systemd.automount").unwrap();
    assert_eq!(item.class(), Some(OptionClass::Userspace));
}

#[test]
fn option_classification_comment() {
    let item = OptItem::new("comment", Some("systemd.automount".to_owned())).unwrap();
    assert_eq!(item.class(), Some(OptionClass::Userspace));
}

#[test]
fn option_classification_verity() {
    let item = OptItem::new("verity.hashdevice", Some("/dev/sda2".to_owned())).unwrap();
    assert_eq!(item.class(), Some(OptionClass::Userspace));
}

#[test]
fn option_classification_loop() {
    let item = OptItem::flag("loop").unwrap();
    assert_eq!(item.class(), Some(OptionClass::Userspace));
}

#[test]
fn vfs_options_filtering() {
    let opts = Options::parse("defaults,ro,noatime,_netdev").unwrap();
    let vfs_names: Vec<&str> = opts.vfs_options().map(|o| o.name()).collect();
    assert!(vfs_names.contains(&"ro"));
    assert!(vfs_names.contains(&"noatime"));
    assert!(!vfs_names.contains(&"defaults")); // userspace, not VFS
    assert!(!vfs_names.contains(&"_netdev")); // userspace, not VFS
}

#[test]
fn user_options_filtering() {
    let opts = Options::parse("defaults,_netdev,nofail").unwrap();
    let user_names: Vec<&str> = opts.user_options().map(|o| o.name()).collect();
    assert!(user_names.contains(&"defaults"));
    assert!(user_names.contains(&"_netdev"));
    assert!(user_names.contains(&"nofail"));
}

#[test]
fn set_value_overwrites_previous() {
    let mut opts = Options::parse("size=10G").unwrap();
    opts.set("size", Some("20G"));
    assert_eq!(opts.get("size"), Some("20G"));
    // Should only have one "size" entry now
    assert_eq!(opts.iter().filter(|o| o.name() == "size").count(), 1);
}

#[test]
fn prepend_puts_option_first() {
    let mut opts = Options::parse("b,c").unwrap();
    opts.prepend(OptItem::flag("a").unwrap());
    let names: Vec<&str> = opts.iter().map(|o| o.name()).collect();
    assert_eq!(names, vec!["a", "b", "c"]);
}

#[test]
fn is_noauto() {
    assert!(Options::parse("noauto").unwrap().is_noauto());
    assert!(!Options::parse("auto").unwrap().is_noauto());
}

#[test]
fn parse_leading_comma_is_error() {
    assert!(Options::parse(",rw").is_err());
}

#[test]
fn parse_consecutive_commas_is_error() {
    assert!(Options::parse("rw,,ro").is_err());
}

#[test]
fn parse_trailing_comma_is_error() {
    assert!(Options::parse("rw,").is_err());
}
