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

use crate::defs::AppDBs;
use pasetoken_lib::ConfigPaSeToken;
use serde_json::json;

#[derive(Clone,Debug)]
pub struct MailMessage {
    pub from: Mailbox,
    pub to: Mailbox,
    pub reply_to: Mailbox,
}
    
impl MailMessage {
     pub fn new(from: &str, to: &str, reply: &str) -> anyhow::Result<Self>  {
        let reply_to = if reply.is_empty() {
           to
        } else {
            reply
        };
        Ok(
            Self { 
                from: from.parse()?,
                to: to.parse()?,
                reply_to: reply_to.parse()?,
            }
        )
    }
    pub fn check(app_dbs: &AppDBs) -> String {
        if app_dbs.config.smtp.is_empty() {
            String::from("Error: no mail server")
        } else if app_dbs.config.mail_from.is_empty() {
            String::from("Error: no mail from address")
        } else {
            String::from("")
        }
    }
    #[allow(dead_code)]
    pub async fn send_message(&self, subject: &str, body: &str, app_dbs: &AppDBs) -> std::io::Result<()> {
        match  Message::builder()
            .from(self.from.to_owned())
            .reply_to(self.reply_to.to_owned())
            .to(self.to.to_owned())
            .subject(subject)
            .header(ContentType::TEXT_PLAIN)
            .body(body.to_owned())
        {
            Ok(message) => self.mail_message(message, app_dbs).await,
            Err(e) => 
                return Err(std::io::Error::new(
                    std::io::ErrorKind::NotFound,
                    format!("ERROR: Invalid mail: {}",e)
                ))
        }
    }
    pub async fn send_html_message(&self, subject: &str, body: &str, html_body: &str, app_dbs: &AppDBs) -> std::io::Result<()> {
        match  Message::builder()
            .from(self.from.to_owned())
            .reply_to(self.reply_to.to_owned())
            .to(self.to.to_owned())
            .subject(subject)
            .multipart(
            MultiPart::alternative() // This is composed of two parts.
                .singlepart(
                    SinglePart::builder()
                        .header(ContentType::TEXT_PLAIN)
                        .body(body.to_owned()),
                )
                .singlepart(
                    SinglePart::builder()
                        .header(ContentType::TEXT_HTML)
                        .body(html_body.to_owned()),
                ),
            )
        {
            Ok(message) => self.mail_message(message, app_dbs).await,
            Err(e) => 
                return Err(std::io::Error::new(
                    std::io::ErrorKind::NotFound,
                    format!("ERROR: Invalid mail: {}",e)
                ))
        }
    }
    pub async fn mail_message(&self, message: Message, app_dbs: &AppDBs) -> std::io::Result<()> {
        let mail_cred = crate::defs::MailMessage::get_credentials(&app_dbs.config.smtp_auth, &app_dbs.config.paseto);
        if ! mail_cred.contains("|") {
            return Err(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("ERROR: Invalid mail credentials")
            )); 
        }
        let auth_data: Vec<String> = mail_cred.split("|").map(|s| s.to_string()).collect();
        if auth_data.len() < 2 {
            return Err(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                format!("ERROR: Invalid mail credentials")
            ));
        }
        let creds = Credentials::new(auth_data[0].to_owned(), auth_data[1].to_owned());
        // Open a remote connection to gmail
        let mailer: AsyncSmtpTransport<Tokio1Executor> =
            AsyncSmtpTransport::<Tokio1Executor>::relay(&app_dbs.config.smtp)
                .unwrap()
                .credentials(creds)
                .build();

        // Send the email
        match mailer.send(message).await {
            Ok(_) => Ok(()),
            Err(e) => Err(std::io::Error::new(
                std::io::ErrorKind::NotConnected,
                format!("ERROR: Could not send email: {e:?}")
            ))
        }
    }
    pub fn get_credentials(token: &str, paseto_config: &ConfigPaSeToken) -> String {
        match paseto_config.pasetoken() {
            Ok(paseto) => {
                match paseto.trusted(token, false) {
                    Ok(trusted_token) => {
                        if let Some(claims) = trusted_token.payload_claims() {
                            claims.get_claim("smtp_auth").unwrap_or(&json!("")).to_string().replace("\"","")
                        } else {
                            String::from("")
                        }
                    },
                    Err(e) => {
                        println!("Token not trusted: {}",e);
                        String::from("")
                    },
                }
            },
            Err(e) => {
                println!("Error collecting notify data: {}",e);
                String::from("")
            }
        }
    }
}