use std::{io, time::Duration};

use crate::listener::{Listener, Endpoint};

static DURATION: Duration = Duration::from_millis(250);

pub struct Bounced<L> {
    listener: L,
}

pub trait BouncedExt: Sized {
    fn bounced(self) -> Bounced<Self> {
        Bounced { listener: self }
    }
}

impl<L> BouncedExt for L { }

fn is_recoverable(e: &io::Error) -> bool {
    matches!(e.kind(),
        | io::ErrorKind::ConnectionRefused
        | io::ErrorKind::ConnectionAborted
        | io::ErrorKind::ConnectionReset)
}

impl<L: Listener + Sync> Bounced<L> {
    #[inline]
    pub async fn accept_next(&self) -> <Self as Listener>::Accept {
        loop {
            match self.listener.accept().await {
                Ok(accept) => return accept,
                Err(e) if is_recoverable(&e) => warn!("recoverable connection error: {e}"),
                Err(e) => {
                    warn!("accept error: {e} [retrying in {}ms]", DURATION.as_millis());
                    tokio::time::sleep(DURATION).await;
                }
            };
        }
    }
}

impl<L: Listener + Sync> Listener for Bounced<L> {
    type Accept = L::Accept;

    type Connection = L::Connection;

    async fn accept(&self) -> io::Result<Self::Accept> {
        Ok(self.accept_next().await)
    }

    async fn connect(&self, accept: Self::Accept) -> io::Result<Self::Connection> {
        self.listener.connect(accept).await
    }

    fn endpoint(&self) -> io::Result<Endpoint> {
        self.listener.endpoint()
    }
}
