use pyo3::IntoPyObjectExt;

use crate::internal::alias;
use crate::internal::lazyheap;
use crate::internal::onceinit;
use crate::internal::utils;
use crate::policies::traits::HandleExt;
use crate::policies::traits::PolicyExt;
use crate::policies::traits::SharedExt;
use crate::policies::vttlpolicy;
use crate::policies::wrapped::Wrapped;

implement_pyclass! {
    /// A cache with a Variable Time-To-Live (VTTL) eviction policy.
    ///
    /// Each item can be inserted with its own individual TTL (time-to-live). When
    /// an item's TTL expires, it is considered stale and will be evicted. Items
    /// inserted without a TTL never expire and are only evicted when the cache
    /// reaches capacity.
    [subclass, extends=crate::pyclasses::base::PyBaseCacheImpl, generic, frozen]
    PyVTTLCache as "VTTLCache" (onceinit::OnceInit<Wrapped<vttlpolicy::VTTLPolicy>>);
}

#[pyo3::pymethods]
impl PyVTTLCache {
    #[new]
    #[allow(unused_variables)]
    #[pyo3(signature=(*args, **kwds))]
    fn __new__(
        args: alias::ArgsType,
        kwds: Option<alias::KwdsType>,
    ) -> pyo3::PyClassInitializer<Self> {
        pyo3::PyClassInitializer::from(crate::pyclasses::base::PyBaseCacheImpl)
            .add_subclass(Self(onceinit::OnceInit::uninit()))
    }

    /// Initialize a new `PyTTLCache` instance.
    ///
    /// Args:
    ///     maxsize: Maximum number of elements the cache can hold.
    ///     iterable: Initial data to populate the cache.
    ///     ttl: Time-to-live duration for `iterable` items. This *is not* a global ttl.
    ///     capacity: Pre-allocate capacity to minimize reallocations. Defaults to 0.
    ///     getsizeof: A callable that computes the size of a key-value pair. When `None`, each
    ///             entry is assumed to have a size of 1 (equivalent to `lambda k, v: 1`).
    ///             Use this to implement weighted caching — for example, sizing entries by
    ///             memory footprint or byte length.
    ///
    /// The cache can be pre-sized via `capacity` to reduce hash table reallocations when
    /// the number of expected entries is known ahead of time.
    #[pyo3(signature=(maxsize, iterable=None, ttl=None, *, capacity=0, getsizeof=None))]
    fn __init__(
        &self,
        py: pyo3::Python,
        maxsize: usize,
        iterable: Option<alias::BoundObject>,
        ttl: Option<utils::TimeToLiveArgument>,
        capacity: usize,
        getsizeof: Option<alias::PyObject>,
    ) -> pyo3::PyResult<()> {
        let wrapped = Wrapped::new(
            vttlpolicy::VTTLPolicy::new(capacity),
            vttlpolicy::Shared::new(maxsize, getsizeof),
        );

        // Populate cache if `iterable` passed
        let extend_result = {
            if let Some(iterable) = iterable {
                let ttl: Option<utils::ExpiresAt> = match ttl {
                    Some(x) => Some(x.into_expires_at()?),
                    None => None,
                };

                let getsizeof = wrapped.shared().getsizeof().clone_ref(py);

                wrapped.extend(
                    // iterable object
                    iterable,
                    // transform function
                    |key, value| vttlpolicy::ExpiringHandle::new(py, &getsizeof, ttl, key, value),
                )
            } else {
                Ok(())
            }
        };

        self.0.set(wrapped);
        extend_result
    }

    #[getter]
    #[inline]
    fn maxsize(&self) -> usize {
        let inner = self.0.get();
        inner.shared().maxsize()
    }

    #[inline]
    fn current_size(&self) -> usize {
        let inner = self.0.get();
        let mut policy = inner.policy();
        policy.expire(inner.shared().generation_version());
        policy.current_size()
    }

    #[inline]
    fn remaining_size(&self) -> usize {
        let inner = self.0.get();
        {
            let mut policy = inner.policy();
            policy.expire(inner.shared().generation_version());
        }

        inner.remaining_size()
    }

    #[getter]
    #[inline]
    fn getsizeof(&self, py: pyo3::Python) -> Option<alias::PyObject> {
        let inner = self.0.get();
        inner.shared().getsizeof().clone_ref(py).into()
    }

    /// Returns the number of elements the map can hold without reallocating.
    #[inline]
    fn capacity(&self) -> usize {
        let inner = self.0.get();
        let policy = inner.policy();

        policy.table().capacity()
    }

    /// Returns the number of entries currently in the cache.
    #[inline]
    fn __len__(&self) -> usize {
        let inner = self.0.get();
        let policy = inner.policy();

        debug_assert!(policy.table().len() == policy.heap().len());
        policy.table().len()
    }

    #[inline]
    fn __sizeof__(&self) -> usize {
        const FIXED_SIZE: usize = size_of::<Wrapped<vttlpolicy::VTTLPolicy>>();

        let inner = self.0.get();
        let policy = inner.policy();

        let table_cap = policy.table().capacity() * 8;
        let list_cap = policy.heap().len() * size_of::<vttlpolicy::ExpiringHandle>();

        FIXED_SIZE + table_cap + list_cap
    }

    #[inline]
    fn __bool__(&self) -> bool {
        let inner = self.0.get();
        let policy = inner.policy();

        !policy.table().is_empty()
    }

    #[inline]
    fn __contains__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<bool> {
        self.contains(py, key)
    }

    /// Returns `true` if the cache contains an entry for `key`.
    #[inline]
    fn contains(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<bool> {
        let key = utils::PrecomputedHashObject::new(py, key)?;
        let inner = self.0.get();
        inner.contains(py, &key)
    }

    /// Returns `True` if cache is empty.
    #[inline]
    fn is_empty(&self) -> bool {
        let inner = self.0.get();
        let policy = inner.policy();

        policy.table().is_empty()
    }

    /// Returns `True` when the cumulative size has reached the maxsize limit.
    #[inline]
    fn is_full(&self) -> bool {
        let inner = self.0.get();
        let shared = inner.shared();
        let policy = inner.policy();

        policy.current_size() >= shared.maxsize()
    }

    /// Equals to `self[key] = value`, but returns a value:
    ///
    /// - If the cache did not have this key present, None is returned.
    /// - If the cache did have this key present, the value is updated,
    ///   and the old value is returned. The key is not updated, though.
    #[pyo3(signature=(key, value, ttl=None))]
    fn insert(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        value: alias::PyObject,
        ttl: Option<utils::TimeToLiveArgument>,
    ) -> pyo3::PyResult<Option<alias::PyObject>> {
        let ttl = match ttl {
            Some(x) => Some(x.into_expires_at()?),
            None => None,
        };

        let inner = self.0.get();
        let shared = inner.shared();
        let handle = vttlpolicy::ExpiringHandle::new(py, shared.getsizeof(), ttl, key, value)?;

        let old_handle = inner.insert(py, handle)?.map(|x| x.into_value());
        Ok(old_handle)
    }

    /// Updates the cache with elements from a dictionary or an iterable object of key/value pairs.
    #[pyo3(signature=(iterable, ttl=None))]
    fn update(
        slf: pyo3::PyRef<'_, Self>,
        py: pyo3::Python,
        iterable: alias::PyObject,
        ttl: Option<utils::TimeToLiveArgument>,
    ) -> pyo3::PyResult<()> {
        if std::ptr::eq(slf.as_ptr(), iterable.as_ptr()) {
            return Ok(());
        }

        let ttl = match ttl {
            Some(x) => Some(x.into_expires_at()?),
            None => None,
        };

        let inner = slf.0.get();
        let shared = inner.shared();
        let getsizeof = shared.getsizeof().clone_ref(py);

        inner.extend(
            // iterable object
            iterable.into_bound(py),
            // transform function
            move |key, value| vttlpolicy::ExpiringHandle::new(py, &getsizeof, ttl, key, value),
        )
    }

    #[inline]
    fn __setitem__(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        value: alias::PyObject,
    ) -> pyo3::PyResult<()> {
        self.insert(py, key, value, None)?;
        Ok(())
    }

    #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined))]
    fn get(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        default: utils::OptionalArgument,
    ) -> pyo3::PyResult<alias::PyObject> {
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();
        let mut policy = inner.policy();

        if let Some(x) = policy.get(py, &key, inner.shared())? {
            return Ok(x.value().clone_ref(py));
        }

        match default {
            utils::OptionalArgument::Defined(x) => Ok(x),
            utils::OptionalArgument::Undefined => unsafe {
                // SAFETY: None is immortal, so reference counting has no meaning
                Ok(pyo3::Bound::from_owned_ptr(py, pyo3::ffi::Py_None()).unbind())
            },
        }
    }

    fn __getitem__(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
    ) -> pyo3::PyResult<alias::PyObject> {
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();
        let mut policy = inner.policy();

        match policy.get(py, &key, inner.shared())? {
            Some(x) => Ok(x.value().clone_ref(py)),
            None => Err(new_py_error!(
                PyKeyError,
                Into::<alias::PyObject>::into(key)
            )),
        }
    }

    #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined, ttl=None))]
    fn setdefault(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        default: utils::OptionalArgument,
        ttl: Option<utils::TimeToLiveArgument>,
    ) -> pyo3::PyResult<alias::PyObject> {
        // 1. Try to get value
        // 2. If exists -> return it
        // 3. Else -> insert default -> return default
        let ttl = match ttl {
            Some(x) => Some(x.into_expires_at()?),
            None => None,
        };
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();
        let shared = inner.shared();
        let mut policy = inner.policy();

        if let Some(x) = policy.get(py, &key, inner.shared())? {
            return Ok(x.value().clone_ref(py));
        }

        let default_object = match default {
            utils::OptionalArgument::Defined(x) => x,
            utils::OptionalArgument::Undefined => unsafe {
                // SAFETY: None is immortal, so reference counting has no meaning
                pyo3::Bound::from_owned_ptr(py, pyo3::ffi::Py_None()).unbind()
            },
        };

        let handle = vttlpolicy::ExpiringHandle::with_precomputed_hash_key(
            py,
            shared.getsizeof(),
            ttl,
            key,
            default_object.clone_ref(py),
        )?;
        inner.insert_no_lock(&mut policy, py, handle)?;
        Ok(default_object)
    }

    #[pyo3(signature = (key, factory, ttl=None))]
    fn setdefault_with(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        factory: alias::PyObject,
        ttl: Option<utils::TimeToLiveArgument>,
    ) -> pyo3::PyResult<alias::PyObject> {
        // 1. Try to get value
        // 2. If exists -> return it
        // 3. Else -> insert default -> return default
        let ttl = match ttl {
            Some(x) => Some(x.into_expires_at()?),
            None => None,
        };
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();
        let shared = inner.shared();

        {
            let mut policy = inner.policy();

            if let Some(x) = policy.get(py, &key, inner.shared())? {
                return Ok(x.value().clone_ref(py));
            }
        }

        // `factory` is Python code: a GC pass inside it would deadlock on `__traverse__`
        let default_object = factory.call0(py)?;

        let mut policy = inner.policy();

        if let Some(x) = policy.get(py, &key, inner.shared())? {
            return Ok(x.value().clone_ref(py));
        }

        let handle = vttlpolicy::ExpiringHandle::with_precomputed_hash_key(
            py,
            shared.getsizeof(),
            ttl,
            key,
            default_object.clone_ref(py),
        )?;
        inner.insert_no_lock(&mut policy, py, handle)?;
        Ok(default_object)
    }

    #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined))]
    fn pop(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        default: utils::OptionalArgument,
    ) -> pyo3::PyResult<alias::PyObject> {
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();

        if let Some(x) = inner.remove(py, &key)? {
            return Ok(x.into_value());
        }

        match default {
            utils::OptionalArgument::Defined(x) => Ok(x),
            utils::OptionalArgument::Undefined => Err(new_py_error!(
                PyKeyError,
                Into::<alias::PyObject>::into(key)
            )),
        }
    }

    fn __delitem__(&self, py: pyo3::Python, key: alias::PyObject) -> pyo3::PyResult<()> {
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();
        match inner.remove(py, &key)? {
            Some(_) => Ok(()),
            None => Err(new_py_error!(
                PyKeyError,
                Into::<alias::PyObject>::into(key)
            )),
        }
    }

    /// Remove and return a (key, value) pair as a 2-tuple.
    fn popitem(&self) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> {
        let inner = self.0.get();
        let mut policy = inner.policy();

        let handle = policy.evict(inner.shared())?;
        drop(policy);

        let (key, val) = handle.into_pair();
        Ok((key.into(), val))
    }

    /// Calls the `popitem()` `n` times and returns count of removed items.
    #[inline]
    fn drain(
        &self,
        py: pyo3::Python,
        n: pyo3::ffi::Py_ssize_t,
    ) -> pyo3::PyResult<pyo3::ffi::Py_ssize_t> {
        let inner = self.0.get();
        inner.drain(py, n)
    }

    /// Shrinks the internal allocation as close to the current length as possible.
    #[inline]
    fn shrink_to_fit(&self) {
        let inner = self.0.get();
        let mut policy = inner.policy();
        policy.shrink_to_fit(inner.shared());
    }

    /// Removes all entries from the table and resets the cumulative size to zero.
    #[pyo3(signature=(*, reuse=false))]
    fn clear(&self, reuse: bool) {
        let inner = self.0.get();
        let shared = inner.shared();
        let mut policy = inner.policy();

        policy.clear(shared);

        if !reuse {
            policy.shrink_to_fit(shared);
        }
    }

    fn __eq__(
        slf: pyo3::PyRef<'_, Self>,
        py: pyo3::Python,
        other: pyo3::PyRef<'_, Self>,
    ) -> pyo3::PyResult<bool> {
        if std::ptr::eq(slf.as_ptr(), other.as_ptr()) {
            return Ok(true);
        }

        let self_inner = slf.0.get();
        let other_inner = other.0.get();

        let self_policy = self_inner.policy();
        let other_policy = other_inner.policy();

        self_policy.py_eq(
            py,
            self_inner.shared(),
            &*other_policy,
            other_inner.shared(),
        )
    }

    fn __ne__(
        slf: pyo3::PyRef<'_, Self>,
        py: pyo3::Python,
        other: pyo3::PyRef<'_, Self>,
    ) -> pyo3::PyResult<bool> {
        if std::ptr::eq(slf.as_ptr(), other.as_ptr()) {
            return Ok(false);
        }

        let self_inner = slf.0.get();
        let other_inner = other.0.get();

        let self_policy = self_inner.policy();
        let other_policy = other_inner.policy();

        self_policy
            .py_eq(
                py,
                self_inner.shared(),
                &*other_policy,
                other_inner.shared(),
            )
            .map(|x| !x)
    }

    fn items(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult<pyo3::Py<PyVTTLCacheItems>> {
        let inner = slf.get().0.get();
        let mut policy = inner.policy();

        let gv = inner.shared().generation_version();
        let iter = policy.iter(gv);

        let result = PyVTTLCacheItems {
            cache: slf.as_any().clone().unbind(),
            iter: parking_lot::Mutex::new(iter),
            gv: gv.clone(),
            initial_gv: gv.get(),
        };
        pyo3::Py::new(slf.py(), result)
    }

    fn values(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult<pyo3::Py<PyVTTLCacheValues>> {
        let inner = slf.get().0.get();
        let mut policy = inner.policy();

        let gv = inner.shared().generation_version();
        let iter = policy.iter(gv);

        let result = PyVTTLCacheValues {
            cache: slf.as_any().clone().unbind(),
            iter: parking_lot::Mutex::new(iter),
            gv: gv.clone(),
            initial_gv: gv.get(),
        };
        pyo3::Py::new(slf.py(), result)
    }

    fn keys(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult<pyo3::Py<PyVTTLCacheKeys>> {
        let inner = slf.get().0.get();
        let mut policy = inner.policy();

        let gv = inner.shared().generation_version();
        let iter = policy.iter(gv);

        let result = PyVTTLCacheKeys {
            cache: slf.as_any().clone().unbind(),
            iter: parking_lot::Mutex::new(iter),
            gv: gv.clone(),
            initial_gv: gv.get(),
        };
        pyo3::Py::new(slf.py(), result)
    }

    #[inline]
    fn __iter__(slf: pyo3::Bound<'_, Self>) -> pyo3::PyResult<pyo3::Py<PyVTTLCacheKeys>> {
        Self::keys(slf)
    }

    fn copy(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<Self>> {
        let inner = self.0.get();
        let cloned = inner.clone_ref(py);

        let result = Self(onceinit::OnceInit::new(cloned));

        pyo3::Py::new(py, (result, crate::pyclasses::base::PyBaseCacheImpl))
    }

    #[inline]
    fn __copy__(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<Self>> {
        self.copy(py)
    }

    fn __getstate__(&self, py: pyo3::Python) -> pyo3::PyResult<alias::PyObject> {
        let inner = self.0.get();
        inner.build_pickle(py).map(|x| x.into())
    }

    fn __setstate__(&self, py: pyo3::Python, state: alias::PyObject) -> pyo3::PyResult<()> {
        let wrapped = Wrapped::from_pickle(py, state)?;
        self.0.set(wrapped);
        Ok(())
    }

    fn __repr__(slf: pyo3::PyRef<'_, Self>, py: pyo3::Python) -> String {
        let inner = slf.0.get();
        let shared = inner.shared();
        let policy = inner.policy();

        let now = std::time::SystemTime::now();

        // We cannot use heap.iter here, because it requires re-sorting
        // and this can lead to intrupt iterators.
        let iter = unsafe {
            policy
                .table()
                .iter()
                .map(|bucket| bucket.as_ref().element())
                .filter(|handle| !handle.is_expired(now))
                .map(|handle| {
                    (
                        // Without `.bind` it returns something like `Py(addr)`
                        handle.key().as_ref().bind(py),
                        handle.value().bind(py),
                    )
                })
        };

        let items = utils::items_to_str(iter, policy.table().len()).unwrap();
        format!(
            "{}[maxsize={}]({})",
            unsafe { utils::get_type_name(py, slf.as_ptr()) },
            shared.maxsize(),
            items
        )
    }

    #[inline]
    #[pyo3(signature=(*, reuse=false))]
    fn expire(&self, reuse: bool) {
        let inner = self.0.get();
        let shared = inner.shared();
        let mut policy = inner.policy();

        policy.expire(shared.generation_version());

        if !reuse {
            policy.shrink_to_fit(shared);
        }
    }

    #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined))]
    fn get_with_expire(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        default: utils::OptionalArgument,
    ) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> {
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();
        let mut policy = inner.policy();

        if let Some(handle) = policy.get(py, &key, inner.shared())? {
            let dur = match handle.expires_at() {
                Some(x) => {
                    let secs = x
                        .duration_since(std::time::SystemTime::now())
                        .unwrap_or_default()
                        .as_secs_f64();

                    secs.into_py_any(py)?
                }
                None => py.None(),
            };

            return Ok((handle.value().clone_ref(py), dur));
        }

        match default {
            utils::OptionalArgument::Defined(x) => Ok((x, py.None())),
            utils::OptionalArgument::Undefined => unsafe {
                // SAFETY: None is immortal, so reference counting has no meaning
                Ok((
                    pyo3::Bound::from_owned_ptr(py, pyo3::ffi::Py_None()).unbind(),
                    py.None(),
                ))
            },
        }
    }

    #[pyo3(signature = (key, default=utils::OptionalArgument::Undefined))]
    fn pop_with_expire(
        &self,
        py: pyo3::Python,
        key: alias::PyObject,
        default: utils::OptionalArgument,
    ) -> pyo3::PyResult<(alias::PyObject, alias::PyObject)> {
        let key = utils::PrecomputedHashObject::new(py, key)?;

        let inner = self.0.get();

        if let Some(handle) = inner.remove(py, &key)? {
            let dur = match handle.expires_at() {
                Some(x) => {
                    let secs = x
                        .duration_since(std::time::SystemTime::now())
                        .unwrap_or_default()
                        .as_secs_f64();

                    secs.into_py_any(py)?
                }
                None => py.None(),
            };

            return Ok((handle.into_value(), dur));
        }

        match default {
            utils::OptionalArgument::Defined(x) => Ok((x, py.None())),
            utils::OptionalArgument::Undefined => Err(new_py_error!(
                PyKeyError,
                Into::<alias::PyObject>::into(key)
            )),
        }
    }

    fn popitem_with_expire(
        &self,
        py: pyo3::Python,
    ) -> pyo3::PyResult<(alias::PyObject, alias::PyObject, alias::PyObject)> {
        let inner = self.0.get();
        let mut policy = inner.policy();

        let handle = policy.evict(inner.shared())?;
        drop(policy);

        let dur = match handle.expires_at() {
            Some(x) => {
                let secs = x
                    .duration_since(std::time::SystemTime::now())
                    .unwrap_or_default()
                    .as_secs_f64();

                secs.into_py_any(py)?
            }
            None => py.None(),
        };

        let (key, val) = handle.into_pair();
        Ok((key.into(), val, dur))
    }

    fn items_with_expire(
        slf: pyo3::Bound<'_, Self>,
    ) -> pyo3::PyResult<pyo3::Py<PyVTTLCacheItemsWithExpire>> {
        let inner = slf.get().0.get();
        let mut policy = inner.policy();

        let gv = inner.shared().generation_version();
        let iter = policy.iter(gv);

        let result = PyVTTLCacheItemsWithExpire {
            cache: slf.as_any().clone().unbind(),
            iter: parking_lot::Mutex::new(iter),
            gv: gv.clone(),
            initial_gv: gv.get(),
        };
        pyo3::Py::new(slf.py(), result)
    }

    fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> {
        if !self.0.is_initialized() {
            return Ok(());
        }

        let inner = self.0.get();
        let policy = inner.policy();

        for cursor in unsafe { policy.table().iter() } {
            let handle = unsafe { cursor.as_ref().element() };

            visit.call(handle.key().as_ref())?;
            visit.call(handle.value())?;
        }
        Ok(())
    }

    fn __clear__(&self) {
        if !self.0.is_initialized() {
            return;
        }

        let inner = self.0.get();
        let mut policy = inner.policy();
        policy.clear(inner.shared());
    }
}

// Implement iterators
macro_rules! implement_iterator {
    (
        $(
            $name:ident as $pyname:literal
            fn ($py:ident, $handle:ident) -> $rt_type:ty { $init:expr }
        )+
    ) => {
        $(
            implement_pyclass! {
                [generic, frozen] $name as $pyname {
                    cache: pyo3::Py<pyo3::PyAny>,
                    initial_gv: u32,
                    gv: utils::GenerationVersion,
                    iter: parking_lot::Mutex<lazyheap::RawIter<vttlpolicy::ExpiringHandle>>,
                }
            }

            implement_view_guard!($name);

            #[pyo3::pymethods]
            impl $name {
                #[inline]
                fn __iter__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> {
                    slf
                }

                fn __traverse__(&self, visit: pyo3::PyVisit<'_>) -> Result<(), pyo3::PyTraverseError> {
                    visit.call(&self.cache)
                }

                fn __next__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<$rt_type> {
                    slf.check_generation()?;

                    let now = std::time::SystemTime::now();
                    let mut iter = slf.iter.lock();
                    let $py = slf.py();

                    while let Some(x) = iter.next() {
                        let $handle = unsafe { x.element() };
                        if $handle.is_expired(now) {
                            continue;
                        }

                        return Ok($init);
                    }

                    Err(new_py_error!(PyStopIteration, ()))
                }

                /// Returns how many not-expired items are left to yield.
                fn __len__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<usize> {
                    slf.check_generation()?;

                    let now = std::time::SystemTime::now();
                    let iter = slf.iter.lock().clone();

                    Ok(iter.filter(|x| !unsafe { x.element() }.is_expired(now)).count())
                }

                /// Returns whether any not-expired item is left, without counting them all.
                fn __bool__(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyResult<bool> {
                    slf.check_generation()?;

                    let now = std::time::SystemTime::now();
                    let mut iter = slf.iter.lock().clone();

                    Ok(iter.any(|x| !unsafe { x.element() }.is_expired(now)))
                }
            }
        )+
    };
}
implement_iterator!(
    PyVTTLCacheItems as "vttlcache_items"
    fn(py, handle) -> (alias::PyObject, alias::PyObject) {{
        let (key, val) = handle.clone_ref(py).into_pair();
        (key.into(), val)
    }}

    PyVTTLCacheItemsWithExpire as "vttlcache_items_with_expire"
    fn(py, handle) -> (alias::PyObject, alias::PyObject, alias::PyObject) {{
        let dur = match handle.expires_at() {
            Some(x) => {
                let secs = x
                    .duration_since(std::time::SystemTime::now())
                    .unwrap_or_default()
                    .as_secs_f64();

                secs.into_py_any(py)?
            }
            None => py.None(),
        };

        let (key, val) = handle.clone_ref(py).into_pair();
        (key.into(), val, dur)
    }}

    PyVTTLCacheKeys as "vttlcache_keys"
    fn(py, handle) -> alias::PyObject { handle.key().clone_ref(py).into() }

    PyVTTLCacheValues as "vttlcache_values"
    fn(py, handle) -> alias::PyObject { handle.value().clone_ref(py) }
);
