//! A milter that prints callback arguments and macros for each stage.

use bytes::Bytes;
use indymilter::{
    Actions, Callbacks, Context, EomContext, Macros, NegotiateContext, ProtoOpts, SocketInfo,
    Status,
};
use std::{env, ffi::CString, process};
use tokio::{net::TcpListener, signal};

#[tokio::main]
async fn main() {
    let args = env::args().collect::<Vec<_>>();

    if args.len() != 2 {
        eprintln!("usage: {} <socket>", args[0]);
        process::exit(1);
    }

    let listener = TcpListener::bind(&args[1])
        .await
        .expect("cannot open milter socket");

    let callbacks = Callbacks::new()
        .on_negotiate(|cx, actions, opts| Box::pin(handle_negotiate(cx, actions, opts)))
        .on_connect(|cx, hostname, socket_info| Box::pin(handle_connect(cx, hostname, socket_info)))
        .on_helo(|cx, hostname| Box::pin(handle_helo(cx, hostname)))
        .on_mail(|cx, args| Box::pin(handle_mail(cx, args)))
        .on_rcpt(|cx, args| Box::pin(handle_rcpt(cx, args)))
        .on_data(|cx| Box::pin(handle_data(cx)))
        .on_header(|cx, name, value| Box::pin(handle_header(cx, name, value)))
        .on_eoh(|cx| Box::pin(handle_eoh(cx)))
        .on_body(|cx, chunk| Box::pin(handle_body(cx, chunk)))
        .on_eom(|cx| Box::pin(handle_eom(cx)))
        .on_abort(|cx| Box::pin(handle_abort(cx)))
        .on_close(|cx| Box::pin(handle_close(cx)))
        .on_unknown(|cx, arg| Box::pin(handle_unknown(cx, arg)));

    let config = Default::default();

    indymilter::run(listener, callbacks, config, signal::ctrl_c())
        .await
        .expect("milter execution failed");
}

async fn handle_negotiate(
    _: &mut NegotiateContext<()>,
    actions: Actions,
    opts: ProtoOpts,
) -> Status {
    println!("NEGOTIATE");
    println!("  actions: {actions:?}");
    println!("  opts: {opts:?}");

    Status::AllOpts
}

async fn handle_connect(
    cx: &mut Context<()>,
    hostname: CString,
    socket_info: SocketInfo,
) -> Status {
    println!("CONNECT");
    println!("  hostname: {hostname:?}");
    println!("  socket_info: {socket_info:?}");
    print_macros(&cx.macros);

    Status::Continue
}

async fn handle_helo(cx: &mut Context<()>, hostname: CString) -> Status {
    println!("HELO");
    println!("  hostname: {hostname:?}");
    print_macros(&cx.macros);

    Status::Continue
}

async fn handle_mail(cx: &mut Context<()>, args: Vec<CString>) -> Status {
    println!("MAIL");
    println!("  args: {args:?}");
    print_macros(&cx.macros);

    Status::Continue
}

async fn handle_rcpt(cx: &mut Context<()>, args: Vec<CString>) -> Status {
    println!("RCPT");
    println!("  args: {args:?}");
    print_macros(&cx.macros);

    Status::Continue
}

async fn handle_data(cx: &mut Context<()>) -> Status {
    println!("DATA");
    print_macros(&cx.macros);

    Status::Continue
}

async fn handle_header(_: &mut Context<()>, name: CString, value: CString) -> Status {
    println!("HEADER");
    println!("  name: {name:?}");
    println!("  value: {value:?}");

    Status::Continue
}

async fn handle_eoh(cx: &mut Context<()>) -> Status {
    println!("EOH");
    print_macros(&cx.macros);

    Status::Continue
}

async fn handle_body(_: &mut Context<()>, chunk: Bytes) -> Status {
    println!("BODY");
    println!("  chunk: {:?}", String::from_utf8_lossy(&chunk));

    Status::Continue
}

async fn handle_eom(cx: &mut EomContext<()>) -> Status {
    println!("EOM");
    print_macros(&cx.macros);

    Status::Continue
}

async fn handle_abort(_: &mut Context<()>) -> Status {
    println!("ABORT");

    Status::Continue
}

async fn handle_close(_: &mut Context<()>) -> Status {
    println!("CLOSE");

    Status::Continue
}

async fn handle_unknown(_: &mut Context<()>, arg: CString) -> Status {
    println!("UNKNOWN");
    println!("  arg: {arg:?}");

    Status::Continue
}

fn print_macros(macros: &Macros) {
    println!("  macros: {:?}", macros.to_hash_map());
}
