//! SMTP transport for email-code login. Two modes:
//!
//! - **Production:** `--smtp-host` (and friends) configured → real SMTP via
//!   `lettre` with optional STARTTLS + auth.
//! - **Dev:** `--smtp-host` empty → the code is logged to stdout instead.
//!   This makes the round-trip testable without standing up a mail server.

use crate::api::state::EmailConfig;
use hbb_common::log;
use lettre::message::header::ContentType;
use lettre::transport::smtp::authentication::Credentials;
use lettre::transport::smtp::AsyncSmtpTransport;
use lettre::{AsyncTransport, Message, Tokio1Executor};

pub async fn send_login_code(
    cfg: Option<&EmailConfig>,
    to: &str,
    code: &str,
) -> Result<(), String> {
    if to.is_empty() {
        return Err("recipient address is empty".into());
    }
    let Some(cfg) = cfg else {
        // Dev mode: surface the code so the operator can complete the flow.
        log::info!("[email-code] login code for <{}>: {}", to, code);
        return Ok(());
    };
    let body = format!(
        "Your login code is: {}\n\nIt expires in 10 minutes.\nIf you didn't request this, ignore this email.\n",
        code
    );
    let message = Message::builder()
        .from(
            cfg.from
                .parse()
                .map_err(|e| format!("invalid From address {:?}: {}", cfg.from, e))?,
        )
        .to(to.parse().map_err(|e| format!("invalid To address {:?}: {}", to, e))?)
        .subject("Your RustDesk login code")
        .header(ContentType::TEXT_PLAIN)
        .body(body)
        .map_err(|e| format!("compose: {}", e))?;

    let mut builder = if cfg.starttls {
        AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&cfg.host)
            .map_err(|e| format!("STARTTLS init for {}: {}", cfg.host, e))?
    } else {
        AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&cfg.host)
    }
    .port(cfg.port);
    if let (Some(user), Some(pass)) = (cfg.username.as_deref(), cfg.password.as_deref()) {
        builder = builder.credentials(Credentials::new(user.to_string(), pass.to_string()));
    }
    let transport = builder.build();
    transport
        .send(message)
        .await
        .map_err(|e| format!("smtp send to {}: {}", cfg.host, e))?;
    log::info!("[email-code] code mailed to <{}>", to);
    Ok(())
}

/// Generate a 6-digit numeric code with cryptographic entropy. Returns the
/// code as a string and its sha256 for storage.
pub fn mint_code() -> (String, Vec<u8>) {
    // Sample 4 random bytes, fold into 0..1_000_000, format as 6-digit
    // zero-padded decimal. 24 bits of entropy is plenty for a 10-minute
    // 5-attempt-limit code.
    let bytes = sodiumoxide::randombytes::randombytes(4);
    let mut n: u32 = 0;
    for b in &bytes {
        n = (n << 8) | (*b as u32);
    }
    let n = n % 1_000_000;
    let code = format!("{:06}", n);
    let hash = sodiumoxide::crypto::hash::sha256::hash(code.as_bytes())
        .as_ref()
        .to_vec();
    (code, hash)
}
