use esp_nvs::error::{
    Error,
    ItemType,
};
use esp_nvs::{
    EntryStatistics,
    Key,
    NvsStatistics,
    PageStatistics,
};
use pretty_assertions::assert_eq;

mod common;

#[test]
fn from_generated_partition() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    let mut nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();

    assert_eq!(
        nvs.get::<u8>(&Key::from_str("namespace_one"), &Key::from_str("example_u8"))
            .unwrap(),
        100
    );
    assert_eq!(
        nvs.get::<i8>(&Key::from_str("namespace_one"), &Key::from_str("example_i8"))
            .unwrap(),
        -100
    );
    assert_eq!(
        nvs.get::<u16>(&Key::from_str("namespace_one"), &Key::from_str("example_u16"))
            .unwrap(),
        65000
    );
    assert_eq!(
        nvs.get::<i16>(&Key::from_str("namespace_one"), &Key::from_str("example_i16"))
            .unwrap(),
        -32000
    );
    assert_eq!(
        nvs.get::<u32>(&Key::from_str("namespace_one"), &Key::from_str("example_u32"))
            .unwrap(),
        4294960000
    );
    assert_eq!(
        nvs.get::<i32>(&Key::from_str("namespace_one"), &Key::from_str("example_i32"))
            .unwrap(),
        -2147480000
    );

    // 64 bit values cant be generated by the python nvs generator so they're skipped here
    // the write test still reads them though

    assert_eq!(
        nvs.get::<String>(&Key::from_str("namespace_one"), &Key::from_str("example_s_short"))
            .unwrap(),
        "short string"
    );
    assert_eq!(
        nvs.get::<String>(&Key::from_str("namespace_one"), &Key::from_str("example_s_long"))
            .unwrap(),
        "long string spanning multiple entries whereas each entry is 32 bytes in total"
    );

    assert_eq!(
        nvs.get::<Vec<u8>>(&Key::from_str("namespace_one"), &Key::from_str("example_b_short"))
            .unwrap(),
        vec![
            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xFF, 0x00, 0xAA,
        ]
    );
    assert_eq!(
        nvs.get::<Vec<u8>>(&Key::from_str("namespace_one"), &Key::from_str("example_b_long"))
            .unwrap(),
        std::fs::read("tests/assets/multi_page_blob.bin").unwrap()
    );

    assert_eq!(
        nvs.get::<u8>(&Key::from_str("namespace_two"), &Key::from_str("example_u8"))
            .unwrap(),
        123
    );
    assert_eq!(
        nvs.get::<u8>(&Key::from_str("namespace_two"), &Key::from_str("only_in_two"))
            .unwrap(),
        1
    );
    assert!(
        nvs.get::<bool>(&Key::from_str("namespace_two"), &Key::from_str("only_in_two"))
            .unwrap()
    );

    // Item type mismatch is reported correctly -> only_in_two is an u8
    assert_eq!(
        nvs.get::<u32>(&Key::from_str("namespace_one"), &Key::from_str("example_u16")),
        Err(Error::ItemTypeMismatch(ItemType::U16))
    );
}

#[test]
fn iter_namespaces() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    let nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();

    assert_eq!(
        nvs.namespaces().collect::<Vec<_>>(),
        vec![&Key::from_array(b"namespace_one"), &Key::from_array(b"namespace_two")]
    );
}

#[test]
fn iter_keys() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    let mut nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();

    assert_eq!(
        nvs.keys().collect::<Vec<_>>(),
        vec![
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_u8"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_i8"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_u16"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_i16"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_u32"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_i32"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_s_short"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_s_long"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_b_short"))),
            Ok((Key::from_array(b"namespace_one"), Key::from_array(b"example_b_long"))),
            Ok((Key::from_array(b"namespace_two"), Key::from_array(b"example_u8"))),
            Ok((Key::from_array(b"namespace_two"), Key::from_array(b"only_in_two")))
        ]
    );
}

#[test]
fn corrupt_page() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    // we manually corrupt header of the first page
    // -> crc invalid
    flash.buf[4] = 123;

    let mut nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();
    let result = nvs.get::<u8>(&Key::from_str("namespace_one"), &Key::from_str("example_u8"));
    assert_eq!(result, Err(Error::NamespaceNotFound));

    assert_eq!(
        nvs.statistics().unwrap(),
        NvsStatistics {
            pages: PageStatistics {
                empty: 1,
                active: 0,
                full: 2,
                erasing: 0,
                corrupted: 1,
            },
            entries_per_page: vec![
                EntryStatistics {
                    empty: 0,
                    written: 0,
                    erased: 0,
                    illegal: 126,
                },
                EntryStatistics {
                    empty: 0,
                    written: 0,
                    erased: 126,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 105,
                    written: 3,
                    erased: 18,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 126,
                    written: 0,
                    erased: 0,
                    illegal: 0,
                },
            ],
            entries_overall: EntryStatistics {
                empty: 231,
                written: 3,
                erased: 144,
                illegal: 126,
            },
        }
    );
}

#[test]
fn corrupt_entry() {
    let mut flash = common::Flash::new_from_file("tests/assets/test_nvs_data.bin");

    // we manually corrupt the example_u8 entry
    // -> crc invalid
    flash.buf[0x60] = 123;

    let mut nvs = esp_nvs::Nvs::new(0, flash.len(), &mut flash).unwrap();
    let result = nvs.get::<u8>(&Key::from_str("namespace_one"), &Key::from_str("example_u8"));
    assert!(result.is_err());

    assert_eq!(result.err().unwrap(), Error::KeyNotFound);

    assert_eq!(
        nvs.statistics().unwrap(),
        NvsStatistics {
            pages: PageStatistics {
                empty: 1,
                active: 0,
                full: 3,
                erasing: 0,
                corrupted: 0,
            },
            entries_per_page: vec![
                EntryStatistics {
                    empty: 0,
                    written: 125,
                    erased: 1,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 0,
                    written: 126,
                    erased: 0,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 105,
                    written: 21,
                    erased: 0,
                    illegal: 0,
                },
                EntryStatistics {
                    empty: 126,
                    written: 0,
                    erased: 0,
                    illegal: 0,
                }
            ],
            entries_overall: EntryStatistics {
                empty: 231,
                written: 272,
                erased: 1,
                illegal: 0,
            },
        }
    );
}

// TODO: when reading a multi-page-blob and the bounds don't match, mark the entry as corrupt

// TODO: when reading a single-page-blob and the bounds don't match, mark the entry as corrupt
// (covers str as well)

// TODO: when the CRC is invalid, mark the entry as corrupt
