/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
 */

use crate::{
    inbound::DkimSign,
    queue::{MessageSource, MessageWrapper, spool::SmtpSpool},
};
use common::{Server, expr::if_block::IfBlock, ipc::ReportingEvent};
use mail_auth::common::headers::HeaderWriter;

pub trait MtaReportSend: Sync + Send {
    fn send_report(
        &self,
        from_addr: &str,
        rcpts: impl Iterator<Item = impl AsRef<str> + Sync + Send> + Sync + Send,
        report: Vec<u8>,
        sign_config: &IfBlock,
        deliver_now: bool,
        parent_session_id: u64,
    ) -> impl Future<Output = ()> + Send;

    fn send_autogenerated(
        &self,
        from_addr: impl AsRef<str> + Sync + Send,
        rcpts: impl Iterator<Item = impl AsRef<str> + Sync + Send> + Sync + Send,
        raw_message: Vec<u8>,
        sign_config: Option<&IfBlock>,
        parent_session_id: u64,
    ) -> impl Future<Output = ()> + Send;

    fn schedule_report(
        &self,
        report: impl Into<ReportingEvent> + Sync + Send,
    ) -> impl Future<Output = ()> + Send;

    fn sign_message(
        &self,
        message: &mut MessageWrapper,
        config: &IfBlock,
        bytes: &[u8],
    ) -> impl Future<Output = Option<Vec<u8>>> + Send;
}

impl MtaReportSend for Server {
    async fn send_report(
        &self,
        from_addr: &str,
        rcpts: impl Iterator<Item = impl AsRef<str> + Sync + Send> + Sync + Send,
        report: Vec<u8>,
        sign_config: &IfBlock,
        deliver_now: bool,
        parent_session_id: u64,
    ) {
        // Build message
        let mut message = self.new_message(from_addr, parent_session_id);
        for rcpt_ in rcpts {
            message.add_expanded_recipient(rcpt_.as_ref(), self).await;
        }

        // Sign message
        let signature = self.sign_message(&mut message, sign_config, &report).await;

        // Schedule delivery at a random time between now and the next 3 hours
        if !deliver_now {
            #[cfg(not(feature = "test_mode"))]
            {
                use common::config::smtp::queue::QueueExpiry;
                use rand::Rng;

                let delivery_time = rand::rng().random_range(0u64..10800u64);
                for rcpt in &mut message.message.recipients {
                    rcpt.retry.due += delivery_time;
                    rcpt.notify.due += delivery_time;
                    if let QueueExpiry::Ttl(expires) = &mut rcpt.expires {
                        *expires += delivery_time;
                    }
                }
            }
        }

        // Queue message
        message
            .queue(
                signature.as_deref(),
                &report,
                parent_session_id,
                self,
                MessageSource::Report,
            )
            .await;
    }

    async fn send_autogenerated(
        &self,
        from_addr: impl AsRef<str> + Sync + Send,
        rcpts: impl Iterator<Item = impl AsRef<str> + Sync + Send> + Sync + Send,
        raw_message: Vec<u8>,
        sign_config: Option<&IfBlock>,
        parent_session_id: u64,
    ) {
        // Build message
        let mut message = self.new_message(from_addr.as_ref(), parent_session_id);
        for rcpt in rcpts {
            message.add_expanded_recipient(rcpt, self).await;
        }

        // Sign message
        let signature = if let Some(sign_config) = sign_config {
            self.sign_message(&mut message, sign_config, &raw_message)
                .await
        } else {
            None
        };

        // Queue message
        message
            .queue(
                signature.as_deref(),
                &raw_message,
                parent_session_id,
                self,
                MessageSource::Autogenerated,
            )
            .await;
    }

    async fn schedule_report(&self, report: impl Into<ReportingEvent> + Sync + Send) {
        if self.inner.ipc.report_tx.send(report.into()).await.is_err() {
            trc::event!(
                Server(trc::ServerEvent::ThreadError),
                CausedBy = trc::location!(),
                Details = "Failed to send event to ReportScheduler"
            );
        }
    }

    async fn sign_message(
        &self,
        message: &mut MessageWrapper,
        config: &IfBlock,
        bytes: &[u8],
    ) -> Option<Vec<u8>> {
        let sign_with_domain = self
            .eval_if::<String, _>(config, &message.message, message.span_id)
            .await?;

        match self.dkim_signers(&sign_with_domain).await {
            Ok(Some(signers)) => {
                let mut headers = Vec::with_capacity(64);

                for signer in signers.as_ref() {
                    match signer.sign(bytes) {
                        Ok(signature) => {
                            signature.write_header(&mut headers);
                        }
                        Err(err) => {
                            trc::error!(
                                trc::Error::from(err)
                                    .span_id(message.span_id)
                                    .details("Failed to sign message")
                                    .caused_by(trc::location!())
                            );
                        }
                    }
                }

                Some(headers)
            }
            Ok(None) => None,
            Err(err) => {
                trc::error!(
                    err.span_id(message.span_id)
                        .details("Failed to retrieve DKIM signers")
                );
                None
            }
        }
    }
}
