use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;

use crate::domain::cache::CacheAdapter;
use crate::error::{CoreError, InternalError, Result};

struct CacheEntry {
    value: String,
    expires_at: Option<Instant>,
}

pub struct InMemoryAdapter {
    store: Arc<Mutex<HashMap<String, CacheEntry>>>,
}

impl InMemoryAdapter {
    pub fn new() -> Self {
        Self {
            store: Arc::new(Mutex::new(HashMap::new())),
        }
    }
}

impl Default for InMemoryAdapter {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl CacheAdapter for InMemoryAdapter {
    async fn insert(&self, key: &str, value: &str, expiration: u64) -> Result<()> {
        let mut store = self.store.lock().await;
        store.insert(
            key.to_string(),
            CacheEntry {
                value: value.to_string(),
                expires_at: Some(Instant::now() + Duration::from_secs(expiration)),
            },
        );
        Ok(())
    }

    async fn find_one(&self, key: &str) -> Result<Option<String>> {
        let store = self.store.lock().await;

        match store.get(key) {
            Some(entry) => {
                if let Some(expires_at) = entry.expires_at
                    && Instant::now() > expires_at
                {
                    return Ok(None);
                }
                Ok(Some(entry.value.clone()))
            }
            None => Ok(None),
        }
    }

    async fn update(&self, key: &str, value: &str, expiration: u64) -> Result<()> {
        let mut store = self.store.lock().await;
        store.entry(key.to_string()).and_modify(|v| {
            *v = CacheEntry {
                value: value.to_string(),
                expires_at: Some(Instant::now() + Duration::from_secs(expiration)),
            };
        });

        Ok(())
    }

    async fn increment(&self, key: &str) -> Result<u64> {
        let mut store = self.store.lock().await;
        let entry = store.entry(key.to_string()).or_insert(CacheEntry {
            value: "0".to_string(),
            expires_at: None,
        });

        let new_val: u64 = entry.value.parse().unwrap_or(0) + 1;
        entry.value = new_val.to_string();

        Ok(new_val)
    }

    async fn delete_one(&self, key: &str) -> Result<()> {
        let mut store = self.store.lock().await;
        if store.remove(key).is_none() {
            return Err(CoreError::Internal(InternalError::Cache(
                "Failed deleting an entry form cache".to_string(),
            )));
        }

        Ok(())
    }

    async fn flush_all(&self) -> Result<()> {
        let mut store = self.store.lock().await;
        store.clear();

        Ok(())
    }
}
