//! SMTP transport (STARTTLS, port 587) — compatible with ZeptoMail and similar providers.

use async_trait::async_trait;
use lettre::message::header::ContentType;
use lettre::message::{Mailbox, Message, MultiPart, SinglePart};
use lettre::transport::smtp::authentication::Credentials;
use lettre::{AsyncSmtpTransport, AsyncTransport, Tokio1Executor};

use crate::error::{AuthError, AuthResult};

use super::EmailProvider;

/// SMTP connection and envelope settings.
#[derive(Debug, Clone)]
pub struct SmtpConfig {
    pub host: String,
    pub port: u16,
    pub user: String,
    pub pass: String,
    pub from_address: String,
    pub from_name: Option<String>,
}

/// Async SMTP [`EmailProvider`] using STARTTLS.
pub struct SmtpEmailProvider {
    transport: AsyncSmtpTransport<Tokio1Executor>,
    from: Mailbox,
}

impl SmtpEmailProvider {
    /// Build provider from explicit config (for tests or non-env wiring).
    pub fn try_new(config: SmtpConfig) -> Result<Self, String> {
        let creds = Credentials::new(config.user, config.pass);
        let transport = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&config.host)
            .map_err(|e| e.to_string())?
            .port(config.port)
            .credentials(creds)
            .build();

        let addr = config
            .from_address
            .parse()
            .map_err(|e: lettre::address::AddressError| e.to_string())?;
        let from = Mailbox::new(config.from_name, addr);

        Ok(Self { transport, from })
    }

    /// Load from environment when `SMTP_HOST` is set; otherwise `Ok(None)`.
    ///
    /// Required when host is set: `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM_ADDRESS`.
    /// Optional: `SMTP_FROM_NAME`, `SMTP_PORT` (default 587).
    pub fn try_from_env() -> Result<Option<Self>, String> {
        let host = std::env::var("SMTP_HOST")
            .ok()
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty());
        let Some(host) = host else {
            return Ok(None);
        };

        let req = |key: &'static str| {
            std::env::var(key)
                .map_err(|_| format!("{key} is required when SMTP_HOST is set"))
                .map(|s| s.trim().to_string())
                .and_then(|s| {
                    if s.is_empty() {
                        Err(format!("{key} is required when SMTP_HOST is set"))
                    } else {
                        Ok(s)
                    }
                })
        };

        let user = req("SMTP_USER")?;
        let pass = req("SMTP_PASS")?;
        let from_address = req("SMTP_FROM_ADDRESS")?;
        let from_name = std::env::var("SMTP_FROM_NAME")
            .ok()
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty());

        let port: u16 = match std::env::var("SMTP_PORT") {
            Ok(s) => {
                let s = s.trim();
                if s.is_empty() {
                    587
                } else {
                    s.parse()
                        .map_err(|_| format!("invalid SMTP_PORT: expected integer, got {s:?}"))?
                }
            }
            _ => 587,
        };

        Self::try_new(SmtpConfig {
            host,
            port,
            user,
            pass,
            from_address,
            from_name,
        })
        .map(Some)
    }
}

fn build_message(
    from: &Mailbox,
    to: &str,
    subject: &str,
    html: &str,
    text: &str,
) -> Result<Message, String> {
    let to_addr: lettre::Address = to
        .parse()
        .map_err(|e: lettre::address::AddressError| e.to_string())?;
    let to_mb = Mailbox::new(None, to_addr);

    let builder = Message::builder()
        .from(from.clone())
        .to(to_mb)
        .subject(subject);

    let msg = if !html.is_empty() && !text.is_empty() {
        let text_part = SinglePart::builder()
            .header(ContentType::TEXT_PLAIN)
            .body(text.to_string());
        let html_part = SinglePart::builder()
            .header(ContentType::TEXT_HTML)
            .body(html.to_string());
        builder
            .multipart(
                MultiPart::alternative()
                    .singlepart(text_part)
                    .singlepart(html_part),
            )
            .map_err(|e: lettre::error::Error| e.to_string())?
    } else if !html.is_empty() {
        builder
            .singlepart(
                SinglePart::builder()
                    .header(ContentType::TEXT_HTML)
                    .body(html.to_string()),
            )
            .map_err(|e: lettre::error::Error| e.to_string())?
    } else {
        builder
            .singlepart(
                SinglePart::builder()
                    .header(ContentType::TEXT_PLAIN)
                    .body(text.to_string()),
            )
            .map_err(|e: lettre::error::Error| e.to_string())?
    };

    Ok(msg)
}

#[async_trait]
impl EmailProvider for SmtpEmailProvider {
    async fn send(&self, to: &str, subject: &str, html: &str, text: &str) -> AuthResult<()> {
        let msg =
            build_message(&self.from, to, subject, html, text).map_err(AuthError::Internal)?;
        self.transport
            .send(msg)
            .await
            .map_err(|e| AuthError::Internal(e.to_string()))?;
        Ok(())
    }
}
