use async_std::sync::RwLock;
use async_std::task::block_on;
use icee_container_rs::get;
use lapin::options::BasicPublishOptions;
use lapin::{BasicProperties, Channel};

use crate::{rabbitmq::RabbitMq, Message};

pub struct AmqpPublisher {
    id: String,
    exchange: String,
    routing_key: String,
    ack: bool,
    channel: RwLock<Channel>,
}

/// AmqpPublisher holds open channel and publish messages to given exchange/routing_key
///
/// Should publisher lost connection, it awaits reconnect to publish a message
/// yielding no error or panicking
///
/// ```no_run
/// # #[async_std::main]
/// # async fn main() {
/// use icee_rabbit_rs::{AmqpPublisher, Message, FieldTable};
///
/// let publisher = AmqpPublisher::new("exchange".into(), "routing_key".into(), true);
/// let message = Message { data: vec![], headers: FieldTable::default(), };
///
/// publisher.publish(message);
/// # }
/// ```
impl AmqpPublisher {
    #![allow(clippy::new_without_default)]
    pub fn new(exchange: String, routing_key: String, ack: bool) -> Self {
        AmqpPublisher {
            id: routing_key.clone(),
            exchange,
            routing_key,
            ack,
            channel: RwLock::new(block_on(prepare_channel())),
        }
    }

    pub fn with_id(mut self, name: String) -> Self {
        self.id = name;
        self
    }

    pub fn id(&self) -> &str {
        &self.id
    }

    pub async fn publish(&self, message: Message) {
        loop {
            let channel = self.channel.read().await;
            let props = BasicProperties::default();

            if let Ok(ack) = channel
                .basic_publish(
                    &self.exchange,
                    &self.routing_key,
                    BasicPublishOptions::default(),
                    message.data.clone(),
                    props.with_headers(message.headers.clone()),
                )
                .await
            {
                if self.ack {
                    if ack.await.is_ok() {
                        return;
                    }
                } else {
                    return;
                }
            }

            drop(channel);
            let mut guard = self.channel.write().await;
            *guard = prepare_channel().await;
        }
    }
}

async fn prepare_channel() -> Channel {
    loop {
        let conn = get::<RabbitMq>("rabbitmq")
            .await
            .expect("missing rabbitmq service");

        let guard = conn.connection().read().await;
        if let Ok(channel) = guard.create_channel().await {
            return channel;
        }
    }
}
