use crate::{bindings, NdbStr, Note};

#[derive(Debug, Clone)]
pub struct Tag<'n> {
    ptr: *mut bindings::ndb_tag,
    note: Note<'n>,
}

impl<'n> Tag<'n> {
    pub(crate) fn new(ptr: *mut bindings::ndb_tag, note: Note<'n>) -> Self {
        Tag { ptr, note }
    }

    pub fn count(&self) -> u16 {
        unsafe { bindings::ndb_tag_count(self.as_ptr()) }
    }

    pub fn get_unchecked(&self, ind: u16) -> NdbStr<'n> {
        let nstr = unsafe {
            bindings::ndb_tag_str(
                self.note().as_ptr(),
                self.as_ptr(),
                ind as ::std::os::raw::c_int,
            )
        };
        NdbStr::new(nstr, self.note.clone())
    }

    pub fn get(&self, ind: u16) -> Option<NdbStr<'n>> {
        if ind >= self.count() {
            return None;
        }
        Some(self.get_unchecked(ind))
    }

    pub fn note(&self) -> &Note<'n> {
        &self.note
    }

    pub fn as_ptr(&self) -> *mut bindings::ndb_tag {
        self.ptr
    }
}

impl<'a> IntoIterator for Tag<'a> {
    type Item = NdbStr<'a>;
    type IntoIter = TagIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        TagIter::new(self)
    }
}

#[derive(Debug, Clone)]
pub struct Tags<'a> {
    ptr: *mut bindings::ndb_tags,
    note: Note<'a>,
}

impl<'a> IntoIterator for Tags<'a> {
    type Item = Tag<'a>;
    type IntoIter = TagsIter<'a>;

    fn into_iter(self) -> TagsIter<'a> {
        TagsIter::new(self.note().clone())
    }
}

impl<'a> Tags<'a> {
    pub(crate) fn new(ptr: *mut bindings::ndb_tags, note: Note<'a>) -> Self {
        Tags { ptr, note }
    }

    pub fn count(&self) -> u16 {
        unsafe { bindings::ndb_tags_count(self.as_ptr()) }
    }

    pub fn iter(&self) -> TagsIter<'a> {
        TagsIter::new(self.note.clone())
    }

    pub fn note(&self) -> &Note<'a> {
        &self.note
    }

    pub fn as_ptr(&self) -> *mut bindings::ndb_tags {
        self.ptr
    }
}

#[derive(Debug, Clone)]
pub struct TagsIter<'a> {
    iter: bindings::ndb_iterator,
    note: Note<'a>,
}

impl<'a> TagsIter<'a> {
    pub fn new(note: Note<'a>) -> Self {
        let iter = bindings::ndb_iterator {
            note: std::ptr::null_mut(),
            tag: std::ptr::null_mut(),
            index: 0,
        };
        let mut iter = TagsIter { note, iter };
        unsafe {
            bindings::ndb_tags_iterate_start(iter.note.as_ptr(), &mut iter.iter);
        };
        iter
    }

    pub fn tag(&self) -> Option<Tag<'a>> {
        let tag_ptr = unsafe { *self.as_ptr() }.tag;
        if tag_ptr.is_null() {
            None
        } else {
            Some(Tag::new(tag_ptr, self.note().clone()))
        }
    }

    pub fn note(&self) -> &Note<'a> {
        &self.note
    }

    pub fn as_ptr(&self) -> *const bindings::ndb_iterator {
        &self.iter
    }

    pub fn as_mut_ptr(&mut self) -> *mut bindings::ndb_iterator {
        &mut self.iter
    }
}

#[derive(Debug, Clone)]
pub struct TagIter<'a> {
    tag: Tag<'a>,
    index: u16,
}

impl<'a> TagIter<'a> {
    pub fn new(tag: Tag<'a>) -> Self {
        let index = 0;
        TagIter { tag, index }
    }

    pub fn done(&self) -> bool {
        self.index >= self.tag.count()
    }
}

impl<'a> Iterator for TagIter<'a> {
    type Item = NdbStr<'a>;

    fn next(&mut self) -> Option<NdbStr<'a>> {
        let tag = self.tag.get(self.index);
        if tag.is_some() {
            self.index += 1;
            tag
        } else {
            None
        }
    }
}

impl<'a> Iterator for TagsIter<'a> {
    type Item = Tag<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        let res = unsafe { bindings::ndb_tags_iterate_next(self.as_mut_ptr()) };
        if res == 0 {
            None
        } else {
            self.tag()
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::config::Config;
    use crate::test_util;
    use crate::{Filter, Ndb, NdbStrVariant, Transaction};

    #[tokio::test]
    async fn tag_iter_works() {
        let db = "target/testdbs/tag_iter_works";
        test_util::cleanup_db(&db);

        {
            let ndb = Ndb::new(db, &Config::new()).expect("ndb");
            let sub = ndb
                .subscribe(&[Filter::new()
                    .ids([&[
                        0xc5, 0xd9, 0x8c, 0xbf, 0x4b, 0xcd, 0x81, 0x1e, 0x28, 0x66, 0x77, 0x0c,
                        0x3d, 0x38, 0x0c, 0x02, 0x84, 0xce, 0x1d, 0xaf, 0x3a, 0xe9, 0x98, 0x3d,
                        0x22, 0x56, 0x5c, 0xb0, 0x66, 0xcf, 0x2a, 0x19,
                    ]])
                    .build()])
                .expect("sub");
            let waiter = ndb.wait_for_notes(sub, 1);
            ndb.process_event(r#"["EVENT","s",{"id": "c5d98cbf4bcd811e2866770c3d380c0284ce1daf3ae9983d22565cb066cf2a19","pubkey": "083727b7a6051673f399102dc48c229c0ec08186ecd7e54ad0e9116d38429c4f","created_at": 1712517119,"kind": 1,"tags": [["e","b9e548b4aa30fa4ce9edf552adaf458385716704994fbaa9e0aa0042a5a5e01e"],["p","140ee9ff21da6e6671f750a0a747c5a3487ee8835159c7ca863e867a1c537b4f"],["hi","3"]],"content": "hi","sig": "1eed792e4db69c2bde2f5be33a383ef8b17c6afd1411598d0c4618fbdf4dbcb9689354276a74614511907a45eec234e0786733e8a6fbb312e6abf153f15fd437"}]"#).expect("process ok");
            let res = waiter.await.expect("await ok");
            assert_eq!(res.len(), 1);
            let note_key = res[0];
            let txn = Transaction::new(&ndb).expect("txn");
            let note = ndb.get_note_by_key(&txn, note_key).expect("note");
            let tags = note.tags();
            assert_eq!(tags.count(), 3);

            let mut tags_iter = tags.iter();

            let t0 = tags_iter.next().expect("t0");
            let t0_e0 = t0.get(0).expect("e tag ok");
            let t0_e1 = t0.get(1).expect("e id ok");
            assert_eq!(t0.get(2).is_none(), true);
            assert_eq!(t0_e0.variant(), NdbStrVariant::Str("e"));
            assert_eq!(
                t0_e1.variant(),
                NdbStrVariant::Id(&[
                    0xb9, 0xe5, 0x48, 0xb4, 0xaa, 0x30, 0xfa, 0x4c, 0xe9, 0xed, 0xf5, 0x52, 0xad,
                    0xaf, 0x45, 0x83, 0x85, 0x71, 0x67, 0x04, 0x99, 0x4f, 0xba, 0xa9, 0xe0, 0xaa,
                    0x00, 0x42, 0xa5, 0xa5, 0xe0, 0x1e
                ])
            );

            let t1 = tags_iter.next().expect("t1");
            let t1_e0 = t1.get(0).expect("p tag ok");
            let t1_e1 = t1.get(1).expect("p id ok");
            assert_eq!(t1.get(2).is_none(), true);
            assert_eq!(t1_e0.variant(), NdbStrVariant::Str("p"));
            assert_eq!(
                t1_e1.variant(),
                NdbStrVariant::Id(&[
                    0x14, 0x0e, 0xe9, 0xff, 0x21, 0xda, 0x6e, 0x66, 0x71, 0xf7, 0x50, 0xa0, 0xa7,
                    0x47, 0xc5, 0xa3, 0x48, 0x7e, 0xe8, 0x83, 0x51, 0x59, 0xc7, 0xca, 0x86, 0x3e,
                    0x86, 0x7a, 0x1c, 0x53, 0x7b, 0x4f
                ])
            );

            let t2 = tags_iter.next().expect("t2");
            let t2_e0 = t2.get(0).expect("hi tag ok");
            let t2_e1 = t2.get(1).expect("hi value ok");
            assert_eq!(t2.get(2).is_none(), true);
            assert_eq!(t2_e0.variant(), NdbStrVariant::Str("hi"));
            assert_eq!(t2_e1.variant(), NdbStrVariant::Str("3"));

            assert_eq!(tags_iter.next().is_none(), true);
        }
    }
}
