/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */

use crate::{MX, Parameters, RecordSet, ResolverCache, Txt};
use std::{
    borrow::Borrow,
    hash::Hash,
    marker::PhantomData,
    net::{IpAddr, Ipv4Addr, Ipv6Addr},
};

pub struct NoCache<K, V>(PhantomData<(K, V)>);

impl<K, V> ResolverCache<K, V> for NoCache<K, V> {
    fn get<Q>(&self, _: &Q) -> Option<V>
    where
        K: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        None
    }

    fn remove<Q>(&self, _: &Q) -> Option<V>
    where
        K: Borrow<Q>,
        Q: Hash + Eq + ?Sized,
    {
        None
    }

    fn insert(&self, _: K, _: V, _: std::time::Instant) {}
}

impl<P>
    Parameters<
        '_,
        P,
        NoCache<Box<str>, Txt>,
        NoCache<Box<str>, RecordSet<MX>>,
        NoCache<Box<str>, RecordSet<Ipv4Addr>>,
        NoCache<Box<str>, RecordSet<Ipv6Addr>>,
        NoCache<IpAddr, RecordSet<Box<str>>>,
    >
{
    pub fn new(params: P) -> Self {
        Parameters {
            params,
            cache_txt: None,
            cache_mx: None,
            cache_ptr: None,
            cache_ipv4: None,
            cache_ipv6: None,
        }
    }
}

impl<'x, P, TXT, MXX, IPV4, IPV6, PTR> Parameters<'x, P, TXT, MXX, IPV4, IPV6, PTR>
where
    TXT: ResolverCache<Box<str>, Txt>,
    MXX: ResolverCache<Box<str>, RecordSet<MX>>,
    IPV4: ResolverCache<Box<str>, RecordSet<Ipv4Addr>>,
    IPV6: ResolverCache<Box<str>, RecordSet<Ipv6Addr>>,
    PTR: ResolverCache<IpAddr, RecordSet<Box<str>>>,
{
    pub fn with_txt_cache<NewTXT: ResolverCache<Box<str>, Txt>>(
        self,
        cache: &'x NewTXT,
    ) -> Parameters<'x, P, NewTXT, MXX, IPV4, IPV6, PTR> {
        Parameters {
            params: self.params,
            cache_txt: Some(cache),
            cache_mx: self.cache_mx,
            cache_ptr: self.cache_ptr,
            cache_ipv4: self.cache_ipv4,
            cache_ipv6: self.cache_ipv6,
        }
    }

    pub fn with_mx_cache<NewMX: ResolverCache<Box<str>, RecordSet<MX>>>(
        self,
        cache: &'x NewMX,
    ) -> Parameters<'x, P, TXT, NewMX, IPV4, IPV6, PTR> {
        Parameters {
            params: self.params,
            cache_txt: self.cache_txt,
            cache_mx: Some(cache),
            cache_ptr: self.cache_ptr,
            cache_ipv4: self.cache_ipv4,
            cache_ipv6: self.cache_ipv6,
        }
    }

    pub fn with_ptr_cache<NewPTR: ResolverCache<IpAddr, RecordSet<Box<str>>>>(
        self,
        cache: &'x NewPTR,
    ) -> Parameters<'x, P, TXT, MXX, IPV4, IPV6, NewPTR> {
        Parameters {
            params: self.params,
            cache_txt: self.cache_txt,
            cache_mx: self.cache_mx,
            cache_ptr: Some(cache),
            cache_ipv4: self.cache_ipv4,
            cache_ipv6: self.cache_ipv6,
        }
    }

    pub fn with_ipv4_cache<NewIPV4: ResolverCache<Box<str>, RecordSet<Ipv4Addr>>>(
        self,
        cache: &'x NewIPV4,
    ) -> Parameters<'x, P, TXT, MXX, NewIPV4, IPV6, PTR> {
        Parameters {
            params: self.params,
            cache_txt: self.cache_txt,
            cache_mx: self.cache_mx,
            cache_ptr: self.cache_ptr,
            cache_ipv4: Some(cache),
            cache_ipv6: self.cache_ipv6,
        }
    }

    pub fn with_ipv6_cache<NewIPV6: ResolverCache<Box<str>, RecordSet<Ipv6Addr>>>(
        self,
        cache: &'x NewIPV6,
    ) -> Parameters<'x, P, TXT, MXX, IPV4, NewIPV6, PTR> {
        Parameters {
            params: self.params,
            cache_txt: self.cache_txt,
            cache_mx: self.cache_mx,
            cache_ptr: self.cache_ptr,
            cache_ipv4: self.cache_ipv4,
            cache_ipv6: Some(cache),
        }
    }

    pub fn clone_with<NewP>(
        &self,
        params: NewP,
    ) -> Parameters<'x, NewP, TXT, MXX, IPV4, IPV6, PTR> {
        Parameters {
            params,
            cache_txt: self.cache_txt,
            cache_mx: self.cache_mx,
            cache_ptr: self.cache_ptr,
            cache_ipv4: self.cache_ipv4,
            cache_ipv6: self.cache_ipv6,
        }
    }
}

#[cfg(test)]
pub mod test {
    use crate::{
        DnssecStatus, MX, Parameters, RecordSet, ResolverCache, Txt, common::resolver::ToFqdn,
    };
    use std::{
        borrow::Borrow,
        hash::Hash,
        net::{IpAddr, Ipv4Addr, Ipv6Addr},
        sync::Arc,
    };

    pub(crate) struct DummyCache<K, V>(std::sync::Mutex<std::collections::HashMap<K, V>>);

    impl<K: Hash + Eq, V: Clone> DummyCache<K, V> {
        pub fn new() -> Self {
            DummyCache(std::sync::Mutex::new(std::collections::HashMap::new()))
        }
    }

    impl<K: Hash + Eq, V: Clone> ResolverCache<K, V> for DummyCache<K, V> {
        fn get<Q>(&self, key: &Q) -> Option<V>
        where
            K: Borrow<Q>,
            Q: Hash + Eq + ?Sized,
        {
            self.0.lock().unwrap().get(key).cloned()
        }

        fn remove<Q>(&self, key: &Q) -> Option<V>
        where
            K: Borrow<Q>,
            Q: Hash + Eq + ?Sized,
        {
            self.0.lock().unwrap().remove(key)
        }

        fn insert(&self, key: K, value: V, _: std::time::Instant) {
            self.0.lock().unwrap().insert(key, value);
        }
    }

    pub(crate) struct DummyCaches {
        pub txt: DummyCache<Box<str>, Txt>,
        pub mx: DummyCache<Box<str>, RecordSet<MX>>,
        pub ptr: DummyCache<IpAddr, RecordSet<Box<str>>>,
        pub ipv4: DummyCache<Box<str>, RecordSet<Ipv4Addr>>,
        pub ipv6: DummyCache<Box<str>, RecordSet<Ipv6Addr>>,
    }

    impl DummyCaches {
        pub fn new() -> Self {
            Self {
                txt: DummyCache::new(),
                mx: DummyCache::new(),
                ptr: DummyCache::new(),
                ipv4: DummyCache::new(),
                ipv6: DummyCache::new(),
            }
        }

        pub fn with_txt(
            self,
            name: impl ToFqdn,
            value: impl Into<Txt>,
            valid_until: std::time::Instant,
        ) -> Self {
            self.txt.insert(name.to_fqdn(), value.into(), valid_until);
            self
        }

        pub fn txt_add(
            &self,
            name: impl ToFqdn,
            value: impl Into<Txt>,
            valid_until: std::time::Instant,
        ) {
            self.txt.insert(name.to_fqdn(), value.into(), valid_until);
        }

        pub fn ipv4_add(
            &self,
            name: impl ToFqdn,
            value: Vec<Ipv4Addr>,
            valid_until: std::time::Instant,
        ) {
            self.ipv4.insert(
                name.to_fqdn(),
                RecordSet {
                    rrset: Arc::from(value.into_boxed_slice()),
                    dnssec_status: DnssecStatus::Indeterminate,
                },
                valid_until,
            );
        }

        pub fn ipv6_add(
            &self,
            name: impl ToFqdn,
            value: Vec<Ipv6Addr>,
            valid_until: std::time::Instant,
        ) {
            self.ipv6.insert(
                name.to_fqdn(),
                RecordSet {
                    rrset: Arc::from(value.into_boxed_slice()),
                    dnssec_status: DnssecStatus::Indeterminate,
                },
                valid_until,
            );
        }

        pub fn ptr_add(&self, name: IpAddr, value: Vec<Box<str>>, valid_until: std::time::Instant) {
            self.ptr.insert(
                name,
                RecordSet {
                    rrset: Arc::from(value.into_boxed_slice()),
                    dnssec_status: DnssecStatus::Indeterminate,
                },
                valid_until,
            );
        }

        pub fn mx_add(&self, name: impl ToFqdn, value: Vec<MX>, valid_until: std::time::Instant) {
            self.mx.insert(
                name.to_fqdn(),
                RecordSet {
                    rrset: Arc::from(value.into_boxed_slice()),
                    dnssec_status: DnssecStatus::Indeterminate,
                },
                valid_until,
            );
        }

        #[allow(clippy::type_complexity)]
        pub fn parameters<T>(
            &self,
            param: T,
        ) -> Parameters<
            '_,
            T,
            DummyCache<Box<str>, Txt>,
            DummyCache<Box<str>, RecordSet<MX>>,
            DummyCache<Box<str>, RecordSet<Ipv4Addr>>,
            DummyCache<Box<str>, RecordSet<Ipv6Addr>>,
            DummyCache<IpAddr, RecordSet<Box<str>>>,
        > {
            Parameters::new(param)
                .with_txt_cache(&self.txt)
                .with_mx_cache(&self.mx)
                .with_ptr_cache(&self.ptr)
                .with_ipv4_cache(&self.ipv4)
                .with_ipv6_cache(&self.ipv6)
        }
    }
}
