mod common;

pub use crate::common::*;
use bytes::Bytes;
use indymilter::{
    message::{
        command::{Command, OptNegPayload},
        reply::Reply,
        PROTOCOL_VERSION,
    },
    Actions, Callbacks, EitherListener, Listener, ProtoOpts, Status,
};
use std::{
    io,
    task::{Context, Poll},
};
use tokio::{io::Empty, net::TcpListener, sync::oneshot};

struct UnusedListener;

impl Listener for UnusedListener {
    type Io = Empty;

    fn poll_accept(&mut self, _: &mut Context<'_>) -> Poll<io::Result<Self::Io>> {
        Poll::Ready(Ok(tokio::io::empty()))
    }
}

#[tokio::test]
async fn either_listener() {
    init_tracing_subscriber();

    let listener = TcpListener::bind(LOCALHOST).await.unwrap();

    let milter_addr = listener.local_addr().unwrap();

    let listener: EitherListener<_, UnusedListener> = EitherListener::Tcp(listener);

    let callbacks = Callbacks::<()>::new()
        .on_eom(|_| Box::pin(async { Status::Continue }));

    let config = default_config();
    let (shutdown, shutdown_rx) = oneshot::channel();

    let milter = tokio::spawn(indymilter::run(listener, callbacks, config, shutdown_rx));

    let mut client = Client::connect(milter_addr).await.unwrap();

    client
        .write_command(Command::OptNeg(OptNegPayload {
            version: PROTOCOL_VERSION,
            actions: Actions::all(),
            opts: ProtoOpts::all(),
        }))
        .await
        .unwrap();

    let reply = client.read_reply().await.unwrap();
    assert!(matches!(reply, Reply::OptNeg { .. }));

    client
        .write_command(Command::BodyEnd(Bytes::new()))
        .await
        .unwrap();

    let reply = client.read_reply().await.unwrap();
    assert_eq!(reply, Reply::Continue);

    client.write_command(Command::Quit).await.unwrap();

    client.disconnect().await.unwrap();

    // Give the milter a bit of time to process `Quit`.
    yield_a_few_times().await;

    let _ = shutdown.send(());

    milter.await.unwrap().unwrap();
}
