use diesel::prelude::*;
use diesel::result::Error;
use diesel::SqliteConnection;
use std::collections::HashMap;
use std::str::FromStr;
use uuid::Uuid;

use crate::models::BlockProperty;

pub type BlockProperties<'a> = HashMap<String, &'a [u8]>;

pub trait BlockPropertyCrud {
    fn create_block_properties(&self, block_id: &str, properties: BlockProperties) -> Vec<BlockProperty>;
    fn retrieve_block_properties(&self, block_id: &str) -> Vec<BlockProperty>;
    fn update_block_property(&self, block_id: &str, name: String, data: &[u8]) -> BlockProperty;
    fn delete_block_property(&self, block_id: &str, name: String);
}

impl BlockPropertyCrud for SqliteConnection {
    fn create_block_properties(&self, block_id: &str, properties: BlockProperties) -> Vec<BlockProperty> {
        use crate::schema::block_properties::dsl;

        let block_uuid = Uuid::from_str(&block_id).unwrap().as_bytes().to_vec();

        self.transaction::<(), Error, _>(|| {
            for (name, data) in properties {
                let new_property = BlockProperty {
                    block_id: block_uuid.clone(),
                    name,
                    data: data.to_vec(),
                };

                diesel::insert_into(dsl::block_properties)
                    .values(new_property)
                    .execute(self)?;
            }

            Ok(())
        })
        .expect("Could not insert new properties");

        dsl::block_properties
            .filter(dsl::block_id.eq(block_uuid))
            .get_results(self)
            .expect("Could not retrieve the block properties")
    }

    fn retrieve_block_properties(&self, block_id: &str) -> Vec<BlockProperty> {
        use crate::schema::block_properties::dsl;

        let block_uuid = Uuid::from_str(&block_id).unwrap().as_bytes().to_vec();

        dsl::block_properties
            .filter(dsl::block_id.eq(block_uuid))
            .get_results(self)
            .expect(&format!("Could not retrieve properties from block {}", block_id))
    }

    fn update_block_property(&self, block_id: &str, name: String, data: &[u8]) -> BlockProperty {
        use crate::schema::block_properties::dsl;

        let block_uuid = Uuid::from_str(&block_id).unwrap().as_bytes().to_vec();

        diesel::update(dsl::block_properties.find((&block_uuid, &name)))
            .set(dsl::data.eq(data.to_vec()))
            .execute(self)
            .and_then(|_| dsl::block_properties.find((&block_uuid, &name)).first(self))
            .expect(&format!("Could not update property {} of block {}", name, block_id))
    }

    fn delete_block_property(&self, block_id: &str, name: String) {
        use crate::schema::block_properties::dsl;

        let block_uuid = Uuid::from_str(&block_id).unwrap().as_bytes().to_vec();

        diesel::delete(dsl::block_properties.find((&block_uuid, &name)))
            .execute(self)
            .expect(&format!("Could not delete property {} of block {}", name, block_id));
    }
}
