use async_trait::async_trait;
use bytes::Bytes;
use std::sync::Arc;
use std::time::Instant;
use tower::{Layer, Service};

/// Cache entry with metadata
#[derive(Clone, Debug)]
pub struct CacheEntry {
    pub data: Bytes,
    pub inserted_at: Instant,
}

/// Cache invalidation policy
#[derive(Clone, Debug)]
pub enum CachePolicy {
    /// Never expires, cache forever (for immutable resources)
    Immutable,
    /// Time-to-live in seconds
    Ttl(u64),
    /// Must revalidate every time (still useful for offline scenarios)
    NoCache,
}

/// Trait for cache backends - implement this for your storage (memory, SQLite, browser Cache API, etc.)
#[async_trait]
pub trait Cache: Clone + Send + Sync + 'static {
    /// Get an entry from the cache
    async fn get(&self, key: &str) -> Option<CacheEntry>;

    /// Set an entry in the cache with the given policy
    async fn set(&self, key: &str, value: Bytes, policy: CachePolicy);

    /// Invalidate a specific key
    async fn invalidate(&self, key: &str);

    /// Invalidate all keys matching a pattern (e.g., "project:*")
    async fn invalidate_pattern(&self, pattern: &str);
}

/// Tower layer for adding caching to a service
#[derive(Clone)]
pub struct CacheLayer<C, Req> {
    cache: C,
    key_fn: Arc<dyn Fn(&Req) -> Option<String> + Send + Sync>,
    policy_fn: Arc<dyn Fn(&Req) -> CachePolicy + Send + Sync>,
}

impl<C, Req> CacheLayer<C, Req> {
    /// Create a new cache layer
    ///
    /// # Arguments
    /// * `cache` - The cache backend implementation
    /// * `key_fn` - Function to generate cache key from request (return None to skip caching)
    /// * `policy_fn` - Function to determine cache policy for request
    pub fn new<F, P>(cache: C, key_fn: F, policy_fn: P) -> Self
    where
        F: Fn(&Req) -> Option<String> + Send + Sync + 'static,
        P: Fn(&Req) -> CachePolicy + Send + Sync + 'static,
    {
        Self {
            cache,
            key_fn: Arc::new(key_fn),
            policy_fn: Arc::new(policy_fn),
        }
    }
}

impl<S, C, Req> Layer<S> for CacheLayer<C, Req>
where
    C: Clone,
{
    type Service = CacheMiddleware<S, C, Req>;

    fn layer(&self, inner: S) -> Self::Service {
        CacheMiddleware {
            inner,
            cache: self.cache.clone(),
            key_fn: self.key_fn.clone(),
            policy_fn: self.policy_fn.clone(),
        }
    }
}

/// Cache middleware service
#[derive(Clone)]
pub struct CacheMiddleware<S, C, Req> {
    inner: S,
    cache: C,
    key_fn: Arc<dyn Fn(&Req) -> Option<String> + Send + Sync>,
    policy_fn: Arc<dyn Fn(&Req) -> CachePolicy + Send + Sync>,
}

impl<S, C, Req, Resp, Err> Service<Req> for CacheMiddleware<S, C, Req>
where
    S: Service<Req, Response = Resp, Error = Err> + Clone + Send + 'static,
    S::Future: Send + 'static,
    C: Cache,
    Req: Clone + Send + 'static,
    Resp: serde::Serialize + serde::de::DeserializeOwned + Send + 'static,
{
    type Response = Resp;
    type Error = Err;
    type Future = futures_util::future::BoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(
        &mut self,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Req) -> Self::Future {
        let cache = self.cache.clone();
        let key = (self.key_fn)(&req);
        let policy = (self.policy_fn)(&req);
        let mut inner = self.inner.clone();

        Box::pin(async move {
            // Try cache first if key is present
            if let Some(k) = &key {
                if let Some(entry) = cache.get(k).await {
                    // Check if entry is still valid based on policy
                    let valid = match &policy {
                        CachePolicy::Immutable => true,
                        CachePolicy::Ttl(ttl) => entry.inserted_at.elapsed().as_secs() < *ttl,
                        CachePolicy::NoCache => false,
                    };

                    if valid {
                        if let Ok(resp) = serde_json::from_slice(&entry.data) {
                            return Ok(resp);
                        }
                    } else {
                        // Expired, invalidate
                        cache.invalidate(k).await;
                    }
                }
            }

            // Cache miss or invalid - call inner service
            let result = inner.call(req).await?;

            // Store in cache with policy
            if let Some(k) = key {
                if let Ok(bytes) = serde_json::to_vec(&result) {
                    cache.set(&k, Bytes::from(bytes), policy).await;
                }
            }

            Ok(result)
        })
    }
}
