use crate::storage::Storage;
use heed::types::{SerdeRmp, Str};
use heed::{Database, RoTxn};
use std::error::Error;

use nostr_sdk::prelude::*;

type HashTagsDatabase<'a> = Database<Str, SerdeRmp<Vec<&'a str>>>;

const DB_NAME: &str = "hashtags_mapping";

impl Storage {
    fn hashtags_db(
        &self,
    ) -> Result<HashTagsDatabase<'_>, Box<dyn Error + Send + Sync>> {
        let rtxn = self.get_read_txn()?;
        let dbo: Option<HashTagsDatabase> =
            self.env.open_database(&rtxn, Some(DB_NAME))?;

        if dbo.is_some() {
            Ok(dbo.unwrap())
        } else {
            Err(Box::from("Error opening nostr keys db"))
        }
    }

    pub fn create_hashtags_db(
        &self,
    ) -> Result<HashTagsDatabase<'_>, Box<dyn Error + Send + Sync>> {
        let mut wtxn = self.env.write_txn()?;
        let db: HashTagsDatabase =
            self.env.create_database(&mut wtxn, Some(DB_NAME))?;
        wtxn.commit()?;
        Ok(db)
    }

    pub fn hashtag_map(
        &self,
        rtxn: &mut RoTxn,
        tagname: &str,
        event_id_hex: &str,
    ) -> Result<(), Box<dyn Error>> {
        if let Ok(db) = self.hashtags_db() {
            let mut wtxn = self.env.write_txn()?;

            if let Some(mut eids) = db.get(rtxn, tagname)? {
                eids.push(event_id_hex);
                db.put(&mut wtxn, tagname, &eids)?;
            } else {
                let mut eids: Vec<&str> = Vec::new();
                eids.push(event_id_hex);
                db.put(&mut wtxn, tagname, &eids)?;
            }

            wtxn.commit()?;
        }
        Ok(())
    }

    pub fn hashtags_list<'c>(
        &'c self,
        rtxn: &'c mut RoTxn,
    ) -> Result<Vec<&'c str>, Box<dyn Error + Send + Sync>> {
        let mut tags: Vec<&str> = Vec::new();

        for result in self.hashtags_db()?.iter(rtxn)? {
            let (tag, _ids) = result?;
            tags.push(tag);
        }
        Ok(tags)
    }

    pub fn hex_eventids_for_hashtag<'c>(
        &'c self,
        rtxn: &'c mut RoTxn,
        tagname: &str,
    ) -> Result<Vec<&'c str>, Box<dyn Error + Send + Sync>> {
        match self.hashtags_db()?.get(rtxn, tagname)? {
            Some(eids) => Ok(eids),
            None => Ok(Vec::new()),
        }
    }

    pub fn eventids_for_hashtag<'c>(
        &'c self,
        rtxn: &'c mut RoTxn,
        tagname: &str,
    ) -> Result<Vec<EventId>, Box<dyn Error + Send + Sync>> {
        match self.hashtags_db()?.get(rtxn, tagname)? {
            Some(eids) => {
                let event_ids: Vec<EventId> =
                    eids.iter().map(|i| EventId::parse(i).unwrap()).collect();

                Ok(event_ids)
            }
            None => Ok(Vec::new()),
        }
    }
}
