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

use crate::{
    outbound::DeliveryResult,
    queue::{
        Error, ErrorDetails, FROM_AUTHENTICATED, FROM_UNAUTHENTICATED_DMARC, HostResponse,
        MessageSource, MessageWrapper, RCPT_SPAM_PAYLOAD, Status, UnexpectedResponse,
        quota::HasQueueQuota, spool::SmtpSpool,
    },
    reporting::send::MtaReportSend,
};
use common::Server;
use email::message::delivery::{IngestMessage, IngestRecipient, LocalDeliveryStatus, MailDelivery};
use smtp_proto::Response;
use trc::SieveEvent;

impl MessageWrapper {
    pub(super) async fn deliver_local(
        &self,
        rcpt_idxs: &[usize],
        statuses: &mut Vec<DeliveryResult>,
        server: &Server,
    ) {
        // Prepare recipients list
        let mut pending_recipients = Vec::new();
        let mut recipients = Vec::new();
        for &rcpt_idx in rcpt_idxs {
            let rcpt = &self.message.recipients[rcpt_idx];
            let rcpt_addr = rcpt.address();
            recipients.push(IngestRecipient {
                address: rcpt_addr.to_lowercase(),
                orcpt: rcpt.orcpt.as_ref().map(|orcpt| orcpt.to_string()),
                is_spam: rcpt.flags & RCPT_SPAM_PAYLOAD != 0,
            });
            pending_recipients.push((rcpt_idx, rcpt_addr));
        }

        // Deliver message
        let delivery_result = server
            .deliver_message(IngestMessage {
                sender_address: self.message.return_path.to_string(),
                sender_authenticated: self.message.flags
                    & (FROM_UNAUTHENTICATED_DMARC | FROM_AUTHENTICATED)
                    != 0,
                recipients,
                message_blob: self.message.blob_hash.clone(),
                message_size: self.message.size,
                session_id: self.span_id,
            })
            .await;

        // Process delivery results
        for ((rcpt_idx, rcpt_addr), result) in
            pending_recipients.into_iter().zip(delivery_result.status)
        {
            let status = match result {
                LocalDeliveryStatus::Success => Status::Completed(HostResponse {
                    hostname: "localhost".into(),
                    response: Response {
                        code: 250,
                        esc: [2, 1, 5],
                        message: "OK".into(),
                    },
                }),
                LocalDeliveryStatus::TemporaryFailure { reason } => {
                    Status::TemporaryFailure(ErrorDetails {
                        entity: "localhost".into(),
                        details: Error::UnexpectedResponse(UnexpectedResponse {
                            command: format!("RCPT TO:<{rcpt_addr}>").into_boxed_str(),
                            response: Response {
                                code: 451,
                                esc: [4, 3, 0],
                                message: reason.into(),
                            },
                        }),
                    })
                }
                LocalDeliveryStatus::PermanentFailure { code, reason } => {
                    Status::PermanentFailure(ErrorDetails {
                        entity: "localhost".into(),
                        details: Error::UnexpectedResponse(UnexpectedResponse {
                            command: format!("RCPT TO:<{rcpt_addr}>").into_boxed_str(),
                            response: Response {
                                code: 550,
                                esc: code,
                                message: reason.into(),
                            },
                        }),
                    })
                }
            };
            statuses.push(DeliveryResult::account(status, rcpt_idx));
        }

        // Process autogenerated messages
        for autogenerated in delivery_result.autogenerated {
            let mut message = server.new_message(autogenerated.sender_address, self.span_id);
            for rcpt in autogenerated.recipients {
                message.expand_and_add_recipient(rcpt, server).await;
            }

            // Sign message
            let signature = server
                .sign_message(
                    &mut message,
                    &server.core.sieve.sign,
                    &autogenerated.message,
                )
                .await;

            // Queue Message
            message.message.size =
                (autogenerated.message.len() + signature.as_ref().map_or(0, |s| s.len())) as u64;
            if server.has_quota(&mut message).await {
                message
                    .queue(
                        signature.as_deref(),
                        &autogenerated.message,
                        self.span_id,
                        server,
                        MessageSource::Autogenerated,
                    )
                    .await;
            } else {
                trc::event!(
                    Sieve(SieveEvent::QuotaExceeded),
                    SpanId = self.span_id,
                    From = message.message.return_path,
                    To = message
                        .message
                        .recipients
                        .into_iter()
                        .map(|r| trc::Value::from(r.address().to_string()))
                        .collect::<Vec<_>>(),
                );
            }
        }
    }
}
