use std::collections::HashMap;
use std::fmt;

use serde::{Deserialize, Serialize};

use crate::error::{MailerError, Result};
use crate::mime::build_rfc822;
use crate::validation::is_valid_email_format;

/// Email priority level.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
    High,
    Normal,
    Low,
}

impl Default for Priority {
    fn default() -> Self {
        Priority::Normal
    }
}

impl fmt::Display for Priority {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Priority::High => write!(f, "high"),
            Priority::Normal => write!(f, "normal"),
            Priority::Low => write!(f, "low"),
        }
    }
}

/// A parsed email address with local part and domain.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EmailAddress {
    pub local: String,
    pub domain: String,
}

impl EmailAddress {
    /// Parse an email address string like "user@example.com" or "Name <user@example.com>".
    pub fn parse(input: &str) -> Result<Self> {
        let addr = extract_email_address(input)
            .ok_or_else(|| MailerError::InvalidEmail(input.to_string()))?;

        let parts: Vec<&str> = addr.splitn(2, '@').collect();
        if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
            return Err(MailerError::InvalidEmail(input.to_string()));
        }

        Ok(EmailAddress {
            local: parts[0].to_string(),
            domain: parts[1].to_lowercase(),
        })
    }

    /// Return the full address as "local@domain".
    pub fn address(&self) -> String {
        format!("{}@{}", self.local, self.domain)
    }
}

impl fmt::Display for EmailAddress {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}@{}", self.local, self.domain)
    }
}

/// Extract the bare email address from a string that may contain display names or angle brackets.
/// Handles formats like:
/// - "user@example.com"
/// - "<user@example.com>"
/// - "John Doe <user@example.com>"
pub fn extract_email_address(input: &str) -> Option<String> {
    let trimmed = input.trim();

    // Handle null sender
    if trimmed == "<>" {
        return None;
    }

    // Try to extract from angle brackets
    if let Some(start) = trimmed.find('<') {
        if let Some(end) = trimmed.find('>') {
            if end > start {
                let addr = trimmed[start + 1..end].trim();
                if !addr.is_empty() {
                    return Some(addr.to_string());
                }
            }
        }
    }

    // No angle brackets — treat entire string as address if it contains @
    if trimmed.contains('@') {
        return Some(trimmed.to_string());
    }

    None
}

/// An email attachment.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attachment {
    pub filename: String,
    #[serde(with = "serde_bytes_base64")]
    pub content: Vec<u8>,
    pub content_type: String,
    pub content_id: Option<String>,
}

/// Serde helper for base64-encoding Vec<u8> in JSON.
mod serde_bytes_base64 {
    use base64::engine::general_purpose::STANDARD;
    use base64::Engine;
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S: Serializer>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&STANDARD.encode(data))
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
        let s = String::deserialize(deserializer)?;
        STANDARD.decode(s).map_err(serde::de::Error::custom)
    }
}

/// A complete email message.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Email {
    pub from: String,
    pub to: Vec<String>,
    pub cc: Vec<String>,
    pub bcc: Vec<String>,
    pub subject: String,
    pub text: String,
    pub html: Option<String>,
    pub attachments: Vec<Attachment>,
    pub headers: HashMap<String, String>,
    pub priority: Priority,
    pub might_be_spam: bool,
    message_id: Option<String>,
    envelope_from: Option<String>,
}

impl Email {
    /// Create a new email with the minimum required fields.
    pub fn new(from: &str, subject: &str, text: &str) -> Self {
        Email {
            from: from.to_string(),
            to: Vec::new(),
            cc: Vec::new(),
            bcc: Vec::new(),
            subject: subject.to_string(),
            text: text.to_string(),
            html: None,
            attachments: Vec::new(),
            headers: HashMap::new(),
            priority: Priority::Normal,
            might_be_spam: false,
            message_id: None,
            envelope_from: None,
        }
    }

    /// Add a To recipient.
    pub fn add_to(&mut self, email: &str) -> &mut Self {
        self.to.push(email.to_string());
        self
    }

    /// Add a CC recipient.
    pub fn add_cc(&mut self, email: &str) -> &mut Self {
        self.cc.push(email.to_string());
        self
    }

    /// Add a BCC recipient.
    pub fn add_bcc(&mut self, email: &str) -> &mut Self {
        self.bcc.push(email.to_string());
        self
    }

    /// Set the HTML body.
    pub fn set_html(&mut self, html: &str) -> &mut Self {
        self.html = Some(html.to_string());
        self
    }

    /// Add an attachment.
    pub fn add_attachment(&mut self, attachment: Attachment) -> &mut Self {
        self.attachments.push(attachment);
        self
    }

    /// Add a custom header.
    pub fn add_header(&mut self, name: &str, value: &str) -> &mut Self {
        self.headers.insert(name.to_string(), value.to_string());
        self
    }

    /// Set email priority.
    pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
        self.priority = priority;
        self
    }

    /// Get the sender domain.
    pub fn from_domain(&self) -> Option<String> {
        EmailAddress::parse(&self.from)
            .ok()
            .map(|addr| addr.domain)
    }

    /// Get the sender address (bare email, no display name).
    pub fn from_address(&self) -> Option<String> {
        extract_email_address(&self.from)
    }

    /// Get all recipients (to + cc + bcc), deduplicated.
    pub fn all_recipients(&self) -> Vec<String> {
        let mut seen = std::collections::HashSet::new();
        let mut result = Vec::new();
        for addr in self.to.iter().chain(self.cc.iter()).chain(self.bcc.iter()) {
            let lower = addr.to_lowercase();
            if seen.insert(lower) {
                result.push(addr.clone());
            }
        }
        result
    }

    /// Get the primary (first To) recipient.
    pub fn primary_recipient(&self) -> Option<&str> {
        self.to.first().map(|s| s.as_str())
    }

    /// Check whether this email has attachments.
    pub fn has_attachments(&self) -> bool {
        !self.attachments.is_empty()
    }

    /// Get total attachment size in bytes.
    pub fn attachments_size(&self) -> usize {
        self.attachments.iter().map(|a| a.content.len()).sum()
    }

    /// Get or generate a Message-ID.
    pub fn message_id(&self) -> String {
        if let Some(ref id) = self.message_id {
            return id.clone();
        }
        let domain = self.from_domain().unwrap_or_else(|| "localhost".to_string());
        let unique = uuid::Uuid::new_v4();
        format!("<{}.{}@{}>", chrono_millis(), unique, domain)
    }

    /// Set an explicit Message-ID.
    pub fn set_message_id(&mut self, id: &str) -> &mut Self {
        self.message_id = Some(id.to_string());
        self
    }

    /// Get the envelope-from (MAIL FROM), falls back to the From header address.
    pub fn envelope_from(&self) -> Option<String> {
        self.envelope_from
            .clone()
            .or_else(|| self.from_address())
    }

    /// Set the envelope-from address.
    pub fn set_envelope_from(&mut self, addr: &str) -> &mut Self {
        self.envelope_from = Some(addr.to_string());
        self
    }

    /// Sanitize a string by removing CR/LF (header injection prevention).
    pub fn sanitize_string(input: &str) -> String {
        input.replace(['\r', '\n'], " ")
    }

    /// Validate all addresses in this email.
    pub fn validate_addresses(&self) -> Vec<String> {
        let mut errors = Vec::new();

        if !is_valid_email_format(&self.from) {
            if extract_email_address(&self.from)
                .map(|a| !is_valid_email_format(&a))
                .unwrap_or(true)
            {
                errors.push(format!("Invalid from address: {}", self.from));
            }
        }

        for addr in &self.to {
            if !is_valid_email_format(addr) {
                if extract_email_address(addr)
                    .map(|a| !is_valid_email_format(&a))
                    .unwrap_or(true)
                {
                    errors.push(format!("Invalid to address: {}", addr));
                }
            }
        }

        for addr in &self.cc {
            if !is_valid_email_format(addr) {
                if extract_email_address(addr)
                    .map(|a| !is_valid_email_format(&a))
                    .unwrap_or(true)
                {
                    errors.push(format!("Invalid cc address: {}", addr));
                }
            }
        }

        for addr in &self.bcc {
            if !is_valid_email_format(addr) {
                if extract_email_address(addr)
                    .map(|a| !is_valid_email_format(&a))
                    .unwrap_or(true)
                {
                    errors.push(format!("Invalid bcc address: {}", addr));
                }
            }
        }

        errors
    }

    /// Convert the email to RFC 5322 format.
    pub fn to_rfc822(&self) -> Result<String> {
        build_rfc822(self)
    }
}

/// Simple epoch millis using std::time (no chrono dependency needed).
fn chrono_millis() -> u128 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis()
}

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

    #[test]
    fn test_email_address_parse() {
        let addr = EmailAddress::parse("user@example.com").unwrap();
        assert_eq!(addr.local, "user");
        assert_eq!(addr.domain, "example.com");

        let addr = EmailAddress::parse("John Doe <john@example.com>").unwrap();
        assert_eq!(addr.local, "john");
        assert_eq!(addr.domain, "example.com");

        let addr = EmailAddress::parse("<admin@test.org>").unwrap();
        assert_eq!(addr.local, "admin");
        assert_eq!(addr.domain, "test.org");
    }

    #[test]
    fn test_extract_email_address() {
        assert_eq!(
            extract_email_address("John <john@example.com>"),
            Some("john@example.com".to_string())
        );
        assert_eq!(
            extract_email_address("user@example.com"),
            Some("user@example.com".to_string())
        );
        assert_eq!(extract_email_address("<>"), None);
        assert_eq!(extract_email_address("no-at-sign"), None);
    }

    #[test]
    fn test_email_new() {
        let mut email = Email::new("sender@example.com", "Test", "Hello");
        email.add_to("recipient@example.com");
        assert_eq!(email.from_domain(), Some("example.com".to_string()));
        assert_eq!(email.all_recipients().len(), 1);
    }

    #[test]
    fn test_all_recipients_dedup() {
        let mut email = Email::new("sender@example.com", "Test", "Hello");
        email.add_to("a@example.com");
        email.add_cc("a@example.com"); // duplicate (case-insensitive)
        email.add_bcc("b@example.com");
        assert_eq!(email.all_recipients().len(), 2);
    }

    #[test]
    fn test_sanitize_string() {
        assert_eq!(Email::sanitize_string("hello\r\nworld"), "hello  world");
        assert_eq!(Email::sanitize_string("normal"), "normal");
    }

    #[test]
    fn test_message_id_generation() {
        let email = Email::new("sender@example.com", "Test", "Hello");
        let mid = email.message_id();
        assert!(mid.starts_with('<'));
        assert!(mid.ends_with('>'));
        assert!(mid.contains("@example.com"));
    }
}
