use anyhow::Result;
use resend_rs::{types::CreateEmailBaseOptions, Resend};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::llms::{
    messages::{Message, Role},
    StructuredLLMProvider,
};

#[derive(Debug, Default)]
pub struct Email {
    from: String,
    subject: String,
    to: Vec<String>,
    cc: Option<Vec<String>>,
    bcc: Option<Vec<String>>,
    html_body: Option<String>,
    text_body: Option<String>,
}

impl Email {
    pub fn new(from: &str, subject: &str, to: Vec<&str>, text_body: &str) -> Self {
        Self {
            from: from.to_string(),
            subject: subject.to_string(),
            to: to.into_iter().map(|s| s.to_string()).collect(),
            text_body: Some(text_body.to_string()),
            ..Default::default()
        }
    }
    pub fn with_cc(mut self, cc: Vec<&str>) -> Self {
        self.cc = Some(cc.into_iter().map(|s| s.to_string()).collect());
        self
    }

    pub fn with_bcc(mut self, bcc: Vec<&str>) -> Self {
        self.bcc = Some(bcc.into_iter().map(|s| s.to_string()).collect());
        self
    }

    pub fn get_to(&self) -> Vec<String> {
        self.to.clone()
    }

    pub fn get_cc(&self) -> Option<Vec<String>> {
        self.cc.clone()
    }

    pub fn get_bcc(&self) -> Option<Vec<String>> {
        self.bcc.clone()
    }

    pub fn get_body(&self) -> Option<String> {
        match (self.html_body.clone(), self.text_body.clone()) {
            (Some(html), None) => Some(html),
            (None, Some(text)) => Some(text),
            _ => None,
        }
    }
}

fn to_resend_email(email: &Email) -> CreateEmailBaseOptions {
    let mut resend_email =
        CreateEmailBaseOptions::new(email.from.clone(), email.to.clone(), email.subject.clone());
    if let Some(cc) = email.cc.clone() {
        for cc_email in cc {
            resend_email = resend_email.with_cc(cc_email.as_str());
        }
    }
    if let Some(bcc) = email.bcc.clone() {
        for bcc_email in bcc {
            resend_email = resend_email.with_bcc(bcc_email.as_str());
        }
    }
    match (email.html_body.clone(), email.text_body.clone()) {
        (Some(html), None) => resend_email.with_html(&html),
        (None, Some(text)) => resend_email.with_text(&text),
        (None, None) => resend_email,
        _ => {
            panic!("Email must have either html or text body, not both");
        }
    }
}

pub fn send_email(email: &Email) -> Result<()> {
    // Ensure dotenv is loaded
    dotenvy::dotenv().ok();

    let resend = Resend::default();

    // Email must be sent in a tokio runtime

    let runtime = tokio::runtime::Runtime::new().unwrap();

    runtime.block_on(async {
        resend.emails.send(to_resend_email(email)).await?;
        Ok(())
    })
}

pub fn write_text_email<M: Message>(
    to: Vec<String>,
    instructions: &str,
    provider: impl StructuredLLMProvider<M>,
) -> Result<Email> {
    // Ask the LLM to write a plain text email with the given instructions
    //
    // LLM needs to return the subject line and the body of the email
    #[derive(Serialize, Deserialize, JsonSchema)]
    struct EmailSchema {
        subject: String,
        body: String,
    }

    let messages = vec![M::new(instructions, Role::User)];

    let email = provider.get_structured_response::<EmailSchema>(messages)?;
    Ok(Email {
        from: "aurora@514.ai".to_string(),
        to,
        subject: email.subject,
        cc: None,
        bcc: None,
        html_body: None,
        text_body: Some(email.body),
    })
}
