use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use hydracache::{cacheable_infallible, cacheable_loader, CacheKeyBuilder, HydraCache, TagSet};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct Profile {
    id: u64,
    name: String,
}

#[tokio::main]
async fn main() -> hydracache::CacheResult<()> {
    let cache = HydraCache::local().build();
    let load_count = Arc::new(AtomicUsize::new(0));
    let profile_id = 42_u64;
    let key = CacheKeyBuilder::new()
        .entity("profile", profile_id)
        .build_string();

    let first: Profile = cacheable_loader!(
        cache = cache,
        key = key.as_str(),
        tags = TagSet::new().tag("profiles").entity("profile", profile_id),
        ttl_secs = 60,
        load = {
            let load_count = Arc::clone(&load_count);
            move || async move {
                load_count.fetch_add(1, Ordering::SeqCst);
                Ok::<_, std::io::Error>(Profile {
                    id: profile_id,
                    name: "Ada".to_owned(),
                })
            }
        },
    )
    .await?;

    let second: Profile = cacheable_loader!(
        cache = cache,
        key = key.as_str(),
        tags = TagSet::new().tag("profiles").entity("profile", profile_id),
        ttl_secs = 60,
        load = {
            let load_count = Arc::clone(&load_count);
            move || async move {
                load_count.fetch_add(1, Ordering::SeqCst);
                Ok::<_, std::io::Error>(Profile {
                    id: profile_id,
                    name: "Grace".to_owned(),
                })
            }
        },
    )
    .await?;

    assert_eq!(first, second);
    assert_eq!(load_count.load(Ordering::SeqCst), 1);

    cache.invalidate_tag("profile:42").await?;
    assert!(!cache.contains_key(key.as_str()).await);

    let count: u64 = cacheable_infallible!(
        cache = cache,
        key = "profiles:count",
        tags = ["profiles"],
        ttl_secs = 60,
        load = || async { 1_u64 },
    )
    .await?;

    assert_eq!(count, 1);
    Ok(())
}
