//! SMTP 会话状态机：逐行解析命令并 dispatch 到 pipeline/handler。
//!
//! 支持命令：EHLO/HELO、MAIL FROM、RCPT TO、DATA、RSET、NOOP、QUIT。
//! 当前实现明文入站 SMTP；STARTTLS 可在需要端到端传输加密时扩展。

use crate::domain::entities::mailbox;
use crate::repositories::audit_repository::{AuditRepository, NewAuditLog};
use crate::smtp::{handler, pipeline};
use crate::AppState;
use std::net::SocketAddr;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::tcp::OwnedReadHalf;

enum ReadDataError {
    TooLarge,
    Eof,
}

/// 从 `FROM:<a@b> PARAM=x` 或 `TO:<a@b>` 中提取尖括号内的地址。
fn parse_addr_arg(arg: &str) -> &str {
    if let Some(start) = arg.find('<') {
        if let Some(end) = arg[start..].find('>') {
            return &arg[start + 1..start + end];
        }
    }
    if let Some(colon) = arg.find(':') {
        return arg[colon + 1..].split_whitespace().next().unwrap_or("");
    }
    ""
}

/// 提取发件域（小写）
fn sender_domain(addr: &str) -> Option<String> {
    let a = addr.trim_matches(|c| c == '<' || c == '>' || c == ' ');
    a.rsplit_once('@')
        .map(|(_, d)| d.to_lowercase())
        .filter(|d| !d.is_empty())
}

/// 读取 DATA 段直到 `<CRLF>.<CRLF>`，处理 dot-stuffing。
async fn read_data(
    reader: &mut BufReader<OwnedReadHalf>,
    max: usize,
) -> Result<Vec<u8>, ReadDataError> {
    let mut data: Vec<u8> = Vec::new();
    let mut line = String::new();
    loop {
        line.clear();
        let n = reader
            .read_line(&mut line)
            .await
            .map_err(|_| ReadDataError::Eof)?;
        if n == 0 {
            return Err(ReadDataError::Eof);
        }
        let trimmed = line.trim_end_matches(['\r', '\n']);
        if trimmed == "." {
            break;
        }
        // dot-stuffing：以 "." 开头的行，去掉行首第一个 "."
        let actual = trimmed.strip_prefix('.').unwrap_or(trimmed);
        data.extend_from_slice(actual.as_bytes());
        data.extend_from_slice(b"\r\n");
        if data.len() > max {
            return Err(ReadDataError::TooLarge);
        }
    }
    Ok(data)
}

/// 运行单个 SMTP 会话。
pub async fn run_session(
    stream: tokio::net::TcpStream,
    peer: SocketAddr,
    state: AppState,
) -> anyhow::Result<()> {
    let peer_ip = peer.ip();
    let (read_half, mut write_half) = stream.into_split();
    let mut reader = BufReader::new(read_half);
    let hostname = state.config.smtp.hostname.clone();
    let max_size = state.config.smtp.max_message_bytes;
    let mut buf = String::new();

    macro_rules! reply {
        ($($arg:tt)*) => {{
            write_half.write_all(format!($($arg)*).as_bytes()).await?;
        }};
    }

    reply!("220 {} ESMTP ready\r\n", hostname);

    // onConnect：IP 限流 + PTR
    match pipeline::check_connect(&state, peer_ip).await {
        pipeline::Verdict::Accept => {}
        v => {
            reply!("{} {}\r\n", v.smtp_code(), v.message());
            return Ok(());
        }
    }

    let mut helo: Option<String> = None;
    let mut mail_from: Option<String> = None;
    let mut rcpt_to: Option<String> = None;
    let mut resolved_mailbox: Option<mailbox::Model> = None;
    let mut honeypot_mode = false;

    loop {
        buf.clear();
        let n = reader.read_line(&mut buf).await?;
        if n == 0 {
            break;
        }
        let line = buf.trim_end_matches(['\r', '\n']);
        if line.is_empty() {
            continue;
        }
        let upper = line.to_ascii_uppercase();
        let (cmd, arg) = match upper.find(' ') {
            Some(i) => (&upper[..i], line[i + 1..].trim()),
            None => (upper.as_str(), ""),
        };

        match cmd {
            "EHLO" | "HELO" => {
                helo = Some(arg.to_string());
                reply!(
                    "250-{}\r\n250-SIZE {}\r\n250-8BITMIME\r\n250 HELP\r\n",
                    hostname,
                    max_size
                );
            }
            "MAIL" => {
                let addr = parse_addr_arg(arg);
                match pipeline::check_mail_from(&state, addr).await {
                    pipeline::Verdict::Accept => {
                        mail_from = Some(addr.to_string());
                        rcpt_to = None;
                        resolved_mailbox = None;
                        reply!("250 2.1.0 sender ok\r\n");
                    }
                    v => reply!("{} {}\r\n", v.smtp_code(), v.message()),
                }
            }
            "RCPT" => {
                if mail_from.is_none() {
                    reply!("503 5.5.1 need MAIL FROM first\r\n");
                    continue;
                }
                let addr = parse_addr_arg(arg);
                match pipeline::check_rcpt_to(
                    &state,
                    peer_ip,
                    mail_from.as_deref().unwrap_or(""),
                    addr,
                )
                .await
                {
                    pipeline::RcptVerdict::Accept(m) => {
                        rcpt_to = Some(addr.to_string());
                        resolved_mailbox = Some(*m);
                        honeypot_mode = false;
                        reply!("250 2.1.5 recipient ok\r\n");
                    }
                    pipeline::RcptVerdict::Honeypot => {
                        rcpt_to = Some(addr.to_string());
                        resolved_mailbox = None;
                        honeypot_mode = true;
                        reply!("250 2.1.5 recipient ok\r\n");
                    }
                    rv => {
                        let v = rv.as_simple();
                        reply!("{} {}\r\n", v.smtp_code(), v.message());
                    }
                }
            }
            "DATA" => {
                if !honeypot_mode && resolved_mailbox.is_none() {
                    reply!("503 5.5.1 need valid RCPT first\r\n");
                    continue;
                }
                reply!("354 end data with <CRLF>.<CRLF>\r\n");
                match read_data(&mut reader, max_size).await {
                    Ok(data) => {
                        if honeypot_mode {
                            // 蜜罐：静默接受、丢弃、永久封禁来源
                            let abuse_svc = crate::services::abuse_service::AbuseService::new(
                                state.pool.clone(),
                                state.config.abuse.clone(),
                            );
                            if let Some(d) = mail_from.as_deref().and_then(sender_domain) {
                                let _ = abuse_svc.auto_block_sender(&d, "honeypot hit").await;
                            }
                            let _ = abuse_svc
                                .auto_block_ip(&peer_ip.to_string(), "honeypot hit")
                                .await;
                            let _ = AuditRepository::new(state.pool.clone())
                                .add(NewAuditLog {
                                    event_type: "smtp_honeypot".into(),
                                    action: "quarantine".into(),
                                    source_ip: Some(peer_ip.to_string()),
                                    helo: helo.clone(),
                                    mail_from: mail_from.clone(),
                                    rcpt_to: rcpt_to.clone(),
                                    reason: Some("honeypot hit".into()),
                                    operator_id: None,
                                    ..Default::default()
                                })
                                .await;
                            crate::workers::alert::notify(
                                &state.config.abuse.alert_webhook_url,
                                &format!(
                                    "蜜罐 {} 被投递，来源 {} 已封禁",
                                    rcpt_to.as_deref().unwrap_or(""),
                                    peer_ip
                                ),
                            )
                            .await;
                            reply!("250 2.0.0 message accepted\r\n");
                            honeypot_mode = false;
                            mail_from = None;
                            rcpt_to = None;
                            continue;
                        }
                        let ctx = handler::DeliveryContext {
                            mailbox: resolved_mailbox.clone().unwrap(),
                            peer_ip: peer_ip.to_string(),
                            helo: helo.clone(),
                            mail_from: mail_from.clone().unwrap_or_default(),
                            rcpt_to: rcpt_to.clone().unwrap_or_default(),
                        };
                        match handler::deliver(&state, &ctx, &data).await {
                            Ok(()) => reply!("250 2.0.0 message accepted\r\n"),
                            Err(e) => {
                                tracing::error!(%e, "邮件入库失败");
                                reply!("451 4.3.0 local error\r\n");
                            }
                        }
                        mail_from = None;
                        rcpt_to = None;
                        resolved_mailbox = None;
                    }
                    Err(ReadDataError::TooLarge) => reply!("552 5.3.4 message too big\r\n"),
                    Err(ReadDataError::Eof) => break,
                }
            }
            "RSET" => {
                mail_from = None;
                rcpt_to = None;
                resolved_mailbox = None;
                reply!("250 2.0.0 ok\r\n");
            }
            "NOOP" => reply!("250 2.0.0 ok\r\n"),
            "QUIT" => {
                reply!("221 2.0.0 {} bye\r\n", hostname);
                break;
            }
            _ => reply!("500 5.5.2 command unrecognized\r\n"),
        }
    }
    Ok(())
}
