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

use common::Server;
use email::delivery::{IngestMessage, LocalDeliveryStatus, MailDelivery};
use smtp_proto::Response;
use trc::SieveEvent;

use crate::{
    queue::{
        quota::HasQueueQuota, spool::SmtpSpool, DomainPart, Error, ErrorDetails, HostResponse,
        Message, MessageSource, Recipient, Status, RCPT_STATUS_CHANGED,
    },
    reporting::SmtpReporting,
};

impl Message {
    pub async fn deliver_local(
        &self,
        recipients: impl Iterator<Item = &mut Recipient>,
        server: &Server,
    ) -> Status<(), Error> {
        // Prepare recipients list
        let mut total_rcpt = 0;
        let mut total_completed = 0;
        let mut pending_recipients = Vec::new();
        let mut recipient_addresses = Vec::new();
        for rcpt in recipients {
            total_rcpt += 1;
            if matches!(
                &rcpt.status,
                Status::Completed(_) | Status::PermanentFailure(_)
            ) {
                total_completed += 1;
                continue;
            }
            recipient_addresses.push(rcpt.address_lcase.clone());
            pending_recipients.push(rcpt);
        }

        // Deliver message
        let delivery_result = server
            .deliver_message(IngestMessage {
                sender_address: self.return_path_lcase.clone(),
                recipients: recipient_addresses,
                message_blob: self.blob_hash.clone(),
                message_size: self.size,
                session_id: self.span_id,
            })
            .await;

        // Process delivery results
        for (rcpt, result) in pending_recipients.into_iter().zip(delivery_result.status) {
            rcpt.flags |= RCPT_STATUS_CHANGED;
            match result {
                LocalDeliveryStatus::Success => {
                    rcpt.status = Status::Completed(HostResponse {
                        hostname: "localhost".to_string(),
                        response: Response {
                            code: 250,
                            esc: [2, 1, 5],
                            message: "OK".to_string(),
                        },
                    });
                    total_completed += 1;
                }
                LocalDeliveryStatus::TemporaryFailure { reason } => {
                    rcpt.status = Status::TemporaryFailure(HostResponse {
                        hostname: ErrorDetails {
                            entity: "localhost".to_string(),
                            details: format!("RCPT TO:<{}>", rcpt.address),
                        },
                        response: Response {
                            code: 451,
                            esc: [4, 3, 0],
                            message: reason.into_owned(),
                        },
                    });
                }
                LocalDeliveryStatus::PermanentFailure { code, reason } => {
                    total_completed += 1;
                    rcpt.status = Status::PermanentFailure(HostResponse {
                        hostname: ErrorDetails {
                            entity: "localhost".to_string(),
                            details: format!("RCPT TO:<{}>", rcpt.address),
                        },
                        response: Response {
                            code: 550,
                            esc: code,
                            message: reason.into_owned(),
                        },
                    });
                }
            }
        }

        // Process autogenerated messages
        for autogenerated in delivery_result.autogenerated {
            let from_addr_lcase = autogenerated.sender_address.to_lowercase();
            let from_addr_domain = from_addr_lcase.domain_part().to_string();

            let mut message = server.new_message(
                autogenerated.sender_address,
                from_addr_lcase,
                from_addr_domain,
                self.span_id,
            );
            for rcpt in autogenerated.recipients {
                message.add_recipient(rcpt, server).await;
            }

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

            // Queue Message
            message.size = autogenerated.message.len() + signature.as_ref().map_or(0, |s| s.len());
            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.return_path_lcase,
                    To = message
                        .recipients
                        .into_iter()
                        .map(|r| trc::Value::from(r.address_lcase))
                        .collect::<Vec<_>>(),
                );
            }
        }

        if total_completed == total_rcpt {
            Status::Completed(())
        } else {
            Status::Scheduled
        }
    }
}
