use mail_auth::spf::verify::SpfParameters;
use mail_auth::{MessageAuthenticator, SpfResult as MailAuthSpfResult};
use serde::{Deserialize, Serialize};
use std::net::IpAddr;

use crate::error::Result;

/// SPF verification result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpfResult {
    /// The SPF result: "pass", "fail", "softfail", "neutral", "temperror", "permerror", "none".
    pub result: String,
    /// The domain that was checked.
    pub domain: String,
    /// The IP address that was checked.
    pub ip: String,
    /// Optional explanation string from the SPF record.
    pub explanation: Option<String>,
}

impl SpfResult {
    /// Whether the SPF check passed.
    pub fn passed(&self) -> bool {
        self.result == "pass"
    }

    /// Create an `SpfResult` from a raw `mail-auth` `SpfOutput`.
    ///
    /// Used by the compound `verify_email_security` to avoid re-doing the SPF query.
    pub fn from_output(output: &mail_auth::SpfOutput, ip: IpAddr) -> Self {
        let result_str = match output.result() {
            MailAuthSpfResult::Pass => "pass",
            MailAuthSpfResult::Fail => "fail",
            MailAuthSpfResult::SoftFail => "softfail",
            MailAuthSpfResult::Neutral => "neutral",
            MailAuthSpfResult::TempError => "temperror",
            MailAuthSpfResult::PermError => "permerror",
            MailAuthSpfResult::None => "none",
        };

        SpfResult {
            result: result_str.to_string(),
            domain: output.domain().to_string(),
            ip: ip.to_string(),
            explanation: output.explanation().map(|s| s.to_string()),
        }
    }
}

/// Check SPF for a given sender IP, HELO domain, and MAIL FROM address.
///
/// * `ip` - The connecting client's IP address
/// * `helo_domain` - The domain from the SMTP EHLO/HELO command
/// * `host_domain` - Your receiving server's hostname
/// * `mail_from` - The full MAIL FROM address (e.g., "sender@example.com")
pub async fn check_spf(
    ip: IpAddr,
    helo_domain: &str,
    host_domain: &str,
    mail_from: &str,
    authenticator: &MessageAuthenticator,
) -> Result<SpfResult> {
    let output = authenticator
        .verify_spf(SpfParameters::verify_mail_from(
            ip,
            helo_domain,
            host_domain,
            mail_from,
        ))
        .await;

    let result_str = match output.result() {
        MailAuthSpfResult::Pass => "pass",
        MailAuthSpfResult::Fail => "fail",
        MailAuthSpfResult::SoftFail => "softfail",
        MailAuthSpfResult::Neutral => "neutral",
        MailAuthSpfResult::TempError => "temperror",
        MailAuthSpfResult::PermError => "permerror",
        MailAuthSpfResult::None => "none",
    };

    Ok(SpfResult {
        result: result_str.to_string(),
        domain: output.domain().to_string(),
        ip: ip.to_string(),
        explanation: output.explanation().map(|s| s.to_string()),
    })
}

/// Check SPF for the EHLO identity (before MAIL FROM).
pub async fn check_spf_ehlo(
    ip: IpAddr,
    helo_domain: &str,
    host_domain: &str,
    authenticator: &MessageAuthenticator,
) -> Result<SpfResult> {
    let output = authenticator
        .verify_spf(SpfParameters::verify_ehlo(ip, helo_domain, host_domain))
        .await;

    let result_str = match output.result() {
        MailAuthSpfResult::Pass => "pass",
        MailAuthSpfResult::Fail => "fail",
        MailAuthSpfResult::SoftFail => "softfail",
        MailAuthSpfResult::Neutral => "neutral",
        MailAuthSpfResult::TempError => "temperror",
        MailAuthSpfResult::PermError => "permerror",
        MailAuthSpfResult::None => "none",
    };

    Ok(SpfResult {
        result: result_str.to_string(),
        domain: helo_domain.to_string(),
        ip: ip.to_string(),
        explanation: output.explanation().map(|s| s.to_string()),
    })
}

/// Build a Received-SPF header value.
pub fn received_spf_header(result: &SpfResult) -> String {
    format!(
        "{} (domain of {} designates {} as permitted sender) receiver={}; client-ip={};",
        result.result,
        result.domain,
        result.ip,
        result.domain,
        result.ip,
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_spf_result_passed() {
        let result = SpfResult {
            result: "pass".to_string(),
            domain: "example.com".to_string(),
            ip: "1.2.3.4".to_string(),
            explanation: None,
        };
        assert!(result.passed());

        let result = SpfResult {
            result: "fail".to_string(),
            domain: "example.com".to_string(),
            ip: "1.2.3.4".to_string(),
            explanation: None,
        };
        assert!(!result.passed());
    }

    #[test]
    fn test_received_spf_header() {
        let result = SpfResult {
            result: "pass".to_string(),
            domain: "example.com".to_string(),
            ip: "1.2.3.4".to_string(),
            explanation: None,
        };
        let header = received_spf_header(&result);
        assert!(header.contains("pass"));
        assert!(header.contains("example.com"));
        assert!(header.contains("1.2.3.4"));
    }
}
