use serde::Serialize;
use serde::de::DeserializeOwned;
use sqlx::SqlitePool;

use crate::errors::*;


pub async fn save<T>(pool: &SqlitePool, name: &str, value: &T) -> Result<(), sqlx::Error>
    where T: Serialize + ?Sized
{
    let bytes = serde_json::to_vec(value).unwrap();
    save_bytes(pool, name, &bytes).await
}


pub async fn save_bytes(pool: &SqlitePool, name: &str, bytes: &[u8]) -> Result<(), sqlx::Error> {
    sqlx::query!(
        "INSERT INTO kv (name, value) VALUES (?, ?)
        ON CONFLICT(name) DO UPDATE SET value = excluded.value;",
        name,
        bytes,
    ).execute(pool).await?;
    Ok(())
}


pub async fn load<T>(pool: &SqlitePool, name: &str) -> Result<Option<T>, LoadKvError>
    where T: DeserializeOwned
{
    let v = load_bytes(pool, name)
        .await?
        .map(|bytes| serde_json::from_slice(&bytes))
        .transpose()?;
    Ok(v)
}


pub async fn load_bytes(pool: &SqlitePool, name: &str) -> Result<Option<Vec<u8>>, sqlx::Error> {
    sqlx::query!("SELECT name, value FROM kv WHERE name = ?", name)
        .map(|row| row.value)
        .fetch_optional(pool)
        .await
        .map(|o| o.flatten())
}


// we don't have a need for this right now, but we will some day
#[cfg(test)]
pub async fn delete(pool: &SqlitePool, name: &str) -> Result<(), sqlx::Error> {
    sqlx::query!("DELETE FROM kv WHERE name = ?", name)
        .execute(pool)
        .await?;
    Ok(())
}


pub async fn delete_multi(pool: &SqlitePool, names: &[&str]) -> Result<(), sqlx::Error> {
    let placeholder = names.iter()
        .map(|_| "?")
        .collect::<Vec<&str>>()
        .join(",");
    let query = format!("DELETE FROM kv WHERE name IN ({})", placeholder);

    let mut q = sqlx::query(&query);
    for name in names {
        q = q.bind(name);
    }
    q.execute(pool).await?;
    Ok(())
}


macro_rules! load_bytes_multi {
    (
        $pool:expr,
        $($name:literal),*
    ) => {
        // wrap everything up in an async block for easy short-circuiting...
        async {
            // ...returning a Result...
            Ok::<_, sqlx::Error>(
                //containing an Option...
                Some(
                    // containing a tuple...
                    (
                        // ...with one item for each repetition of $name
                        $(
                            // load_bytes returns Result<Option<_>>, the Result is handled by
                            // the ? and we match on the Option
                            match crate::kv::load_bytes($pool, $name).await? {
                                Some(v) => v,
                                None => return Ok(None)
                            },
                        )*
                    )
                )
            )
        }
    }
}

pub(crate) use load_bytes_multi;


// macro_rules! load_multi {
//     (
//         $pool:expr,
//         $($name:literal),*
//     ) => {
//         (|| {
//             (
//                 $(
//                     match load(pool, $name)? {
//                         Some(v) => v,
//                         None => return Ok(None)
//                     },
//                 )*
//             )
//         })()
//     }
// }


#[cfg(test)]
mod tests {
    use super::*;


    #[sqlx::test]
    async fn test_save_bytes(pool: SqlitePool) {
        save_bytes(&pool, "test_bytes", b"hello world").await
            .expect("Failed to save bytes");
    }


    #[sqlx::test]
    async fn test_save(pool: SqlitePool) {
        save(&pool, "test_string", "hello world").await
            .expect("Failed to save string");
        save(&pool, "test_int", &123).await
            .expect("Failed to save integer");
        save(&pool, "test_bool", &true).await
            .expect("Failed to save bool");
    }


    #[sqlx::test(fixtures("kv"))]
    async fn test_load_bytes(pool: SqlitePool) {
        let bytes = load_bytes(&pool, "test_bytes").await
            .expect("Failed to load bytes")
            .expect("Test data not found in database");

        assert_eq!(bytes, Vec::from(b"hello world"));
    }


    #[sqlx::test(fixtures("kv"))]
    async fn test_load(pool: SqlitePool) {
        let string: String = load(&pool, "test_string").await
            .expect("Failed to load string")
            .expect("Test data not found in database");
        assert_eq!(string, "hello world".to_string());

        let integer: usize = load(&pool, "test_int").await
            .expect("Failed to load integer")
            .expect("Test data not found in database");
        assert_eq!(integer, 123);

        let boolean: bool = load(&pool, "test_bool").await
            .expect("Failed to load boolean")
            .expect("Test data not found in database");
        assert_eq!(boolean, true);
    }


    #[sqlx::test(fixtures("kv"))]
    async fn test_load_multi(pool: SqlitePool) {
        let (bytes, boolean) = load_bytes_multi!(&pool, "test_bytes", "test_bool")
            .await
            .expect("Failed to load items")
            .expect("Test data not found in database");

        assert_eq!(bytes, Vec::from(b"hello world"));
        assert_eq!(boolean, Vec::from(b"true"));
    }


    #[sqlx::test(fixtures("kv"))]
    async fn test_delete(pool: SqlitePool) {
        delete(&pool, "test_bytes").await
            .expect("Failed to delete data");

        let loaded = load_bytes(&pool, "test_bytes").await
            .expect("Failed to load data");
        assert_eq!(loaded, None);
    }


    #[sqlx::test(fixtures("kv"))]
    async fn test_delete_multi(pool: SqlitePool) {
        delete_multi(&pool, &["test_bytes", "test_string"]).await
            .expect("Failed to delete keys");

        let bytes_opt = load_bytes(&pool, "test_bytes").await
            .expect("Failed to load bytes");
        assert_eq!(bytes_opt, None);

        let string_opt = load_bytes(&pool, "test_string").await
            .expect("Failed to load string");
        assert_eq!(string_opt, None);
    }
}
