use cfg_if::cfg_if;
use leptos::*;
use validator::Validate;
use crate::backend::data::{ApiResponse, Message, MessageType};
use crate::components::data_form::ForValidation;

cfg_if! { if #[cfg(feature = "ssr")] {
    use sqlx::{PgPool, query, query_as};
    use sqlx::Error;
    use crate::backend::get_pool;
    use crate::error::AppError;
    use log::info;
    use log::error;
    use crate::config::Mailing;
    use crate::config::MailTransport;
    use crate::backend::data::ResSumWithItems;
    use lettre::message::Message as LettreMessage;
    use lettre::{FileTransport, SmtpTransport, Transport};
    use lettre::transport::smtp::client::{Tls, TlsParameters};
    use lettre::transport::smtp::authentication::Credentials;
    use lettre::transport::smtp::extension::ClientId;
    use std::ops::Add;
    use std::sync::mpsc::Sender;
    use std::sync::mpsc;

    pub async fn message_for_type(msg_type: &MessageType, pool: &PgPool) -> Result<Message, Error> {
        Ok(query_as::<_, Message>("SELECT * FROM message WHERE msg_type = $1")
            .bind(msg_type)
            .fetch_one(pool)
            .await?)
    }

    pub async fn init_message(msg_type: &MessageType, pool: &PgPool) -> Result<(), Error> {
        query("INSERT INTO message(msg_type, subject, text) VALUES($1, '', '')")
            .bind(msg_type)
            .execute(pool)
            .await?;

        Ok(())
    }

    pub async fn check_messages(pool: &PgPool) -> Result<(), AppError> {
        let types = [
            MessageType::NewReservation,
            MessageType::NewReservationCust,
            MessageType::ReservationApp,
            MessageType::ReservationCanceled];

        for msg_type in types {
            let msg = message_for_type(&msg_type, pool).await;
            if let Err(e) = msg {
                if matches!(e, Error::RowNotFound) {
                    info!("Creating initial message for type {:?}", msg_type);
                    init_message(&msg_type, pool).await?;
                } else {
                    return Err(e.into())
                }
            }
        }

        Ok(())
    }

    pub struct MailMessage {
        reply_to: String,
        to: String,
        subject: String,
        text: String
    }

    impl MailMessage {
        pub fn new(reply_to: String, to: String, message: Message, reservation: &ResSumWithItems) -> Self {
            Self {
                reply_to,
                to,
                subject: message.subject,
                text: Self::replace_body_vars(message.text, &reservation)
            }
        }

        fn replace_body_vars(text: String, reservation: &ResSumWithItems) -> String {
            text
                .replace("#date#", &reservation.summary.date.format("%d. %m. %Y").to_string())
                .replace("#summary#", &{
                    let mut sum = "".to_string();
                    for p in &reservation.reservations {
                        sum = sum.add(&format!("{}: {} - {}",
                            p.property.name,
                            p.reservation.from.format("%H:%M").to_string(), p.reservation.to.format("%H:%M").to_string()));
                    }
                    sum
                }
            )
        }

        pub fn build_mail(&self, from: String) -> Result<LettreMessage, AppError> {
            Ok(LettreMessage::builder()
                .from(from.parse()?)
                .reply_to(self.reply_to.parse()?)
                .to(self.to.parse()?)
                .subject(&self.subject)
                .body(self.text.clone())?)
        }
    }

    #[derive(Clone)]
    enum MailerType {
        Smtp(SmtpTransport),
        File(String)
    }

    #[derive(Clone)]
    pub struct Mailer {
        transport: MailerType,
        from: String,
        sender: Option<Sender<MailMessage>>
    }

    impl Mailer {
        pub fn new(config: &Mailing) -> Self {
            match config.transport() {
                MailTransport::Smtp => {
                    let tls = if let Some(t) = config.accept_all_certs() {
                        if t {
                            let tls = TlsParameters::builder(config.server().clone().unwrap_or_default())
                            .dangerous_accept_invalid_certs(true)
                            .dangerous_accept_invalid_hostnames(true);
                        Some(tls.build().expect("Cannot build TLS params"))
                        } else {
                            None
                        }
                    } else {
                        None
                    };
                    let transport = if config.tls().unwrap_or(false) {
                        let transport = SmtpTransport::starttls_relay(&config.server().clone().unwrap_or_default())
                            .expect("Cannot create SMTP mail transport");
                        if let Some(t) = tls {
                            transport.tls(Tls::Required(t))
                        } else {
                            transport
                        }
                    } else {
                        let transport = SmtpTransport::relay(&config.server().clone().unwrap_or_default())
                            .expect("Cannot create SMTP mail transport");
                        if let Some(t) = tls {
                            transport.tls(Tls::Wrapper(t))
                        } else {
                            transport
                        }
                    };
                    let transport = if let Some(p) = config.port() {
                        transport.port(p)
                    } else {
                        transport
                    };
                    let transport = if let Some(hello) = config.hello_name() {
                        transport.hello_name(ClientId::Domain(hello.to_string()))
                    } else {
                        transport
                    };
                    if config.user().is_some() && config.password().is_some() {
                        let cred = Credentials::new(config.user().clone().unwrap(), config.password().clone().unwrap());
                        Self {
                            transport: MailerType::Smtp(transport.credentials(cred).build()),
                            from: config.from().to_string(),
                            sender: None
                        }
                    } else {
                        Self {
                            transport: MailerType::Smtp(transport.build()),
                            from: config.from().to_string(),
                            sender: None
                        }
                    }
                }
                MailTransport::File => {
                    Self {
                        transport: MailerType::File(config.path().clone().unwrap_or("".to_string())),
                        from: config.from().to_string(),
                        sender: None
                    }
                }
            }
        }

        fn send(&self, msg: &MailMessage) -> Result<(), AppError> {
            let to_send = msg.build_mail(self.from.clone())?;
            match &self.transport {
                MailerType::Smtp(s) => {s.send(&to_send)?;},
                MailerType::File(s) => {FileTransport::new(s).send(&to_send)?;}
            }
            Ok(())
        }

        pub fn start_sender(&mut self) {
            let (tx, rx) = mpsc::channel::<MailMessage>();
            self.sender = Some(tx);
            let mailer = self.clone();

            std::thread::spawn(move || {
                loop {
                    let msg = rx.recv();
                    if let Err(e) = msg {
                        error!("Mailer error: {}", e.to_string());
                        break;
                    }

                    let msg = msg.unwrap();

                    if let Err(e) = mailer.send(&msg) {
                        error!("Mail send error: {}", e);
                    } else {
                        info!("Mail message for: {} has been successfully sent", msg.to);
                    }
                }
            });

            info!("Mail sender started");
        }

        pub fn send_mail(&self, msg: MailMessage) -> Result<(), AppError> {
            if let Err(e) = self.sender.as_ref().expect("Sender not started").send(msg) {
                error!("Mail queue error: {}", e);
                Err(AppError::MailSendError(e.to_string()))
            } else {
                info!("Message queued for send");
                Ok(())
            }
        }
    }
}}

#[server]
pub async fn get_message(msg_type: MessageType) -> Result<Message, ServerFnError> {
    let pool = get_pool().await?;

    Ok(message_for_type(&msg_type, &pool).await?)
}

#[server]
pub async fn update_message(message: Message) -> Result<ApiResponse<()>, ServerFnError> {
    use crate::perm_check;

    perm_check!(is_admin);
    let pool = get_pool().await?;

    query("UPDATE message SET subject = $1, text = $2 WHERE msg_type = $3")
        .bind(message.subject)
        .bind(message.text)
        .bind(message.msg_type)
        .execute(&pool)
        .await?;

    Ok(ApiResponse::Data(()))
}

impl ForValidation for UpdateMessage {
    fn entity(&self) -> &dyn Validate {
        &self.message
    }
}