use std::collections::VecDeque;
use std::time::Duration;

use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::Mutex;
use tokio_condvar::Condvar;

use crate::daemon::events::Event as DaemonEvent;
use kordophone::api::APIInterface;
use kordophone::model::outgoing_message::OutgoingMessage;

use anyhow::Result;

mod target {
    pub static POST_OFFICE: &str = "post_office";
}

#[derive(Debug)]
pub enum Event {
    EnqueueOutgoingMessage(OutgoingMessage),
}

pub struct PostOffice<C: APIInterface, F: AsyncFnMut() -> Result<C>> {
    event_source: Receiver<Event>,
    event_sink: Sender<DaemonEvent>,
    make_client: F,
    message_queue: Mutex<VecDeque<OutgoingMessage>>,
    message_available: Condvar,
}

impl<C: APIInterface, F: AsyncFnMut() -> Result<C>> PostOffice<C, F> {
    pub fn new(
        event_source: Receiver<Event>,
        event_sink: Sender<DaemonEvent>,
        make_client: F,
    ) -> Self {
        Self {
            event_source,
            event_sink,
            make_client,
            message_queue: Mutex::new(VecDeque::new()),
            message_available: Condvar::new(),
        }
    }

    pub async fn queue_message(&mut self, message: &OutgoingMessage) {
        self.message_queue.lock().await.push_back(message.clone());
        self.message_available.notify_one();
    }

    pub async fn run(&mut self) {
        log::info!(target: target::POST_OFFICE, "Starting post office");

        loop {
            let mut retry_messages = Vec::new();
            tokio::select! {
                // Incoming events
                Some(event) = self.event_source.recv() => {
                    match event {
                        Event::EnqueueOutgoingMessage(message) => {
                            log::debug!(target: target::POST_OFFICE, "Received enqueue outgoing message event");
                            self.message_queue.lock().await.push_back(message);
                            self.message_available.notify_one();
                        }
                    }
                }

                // Message queue
                mut lock = self.message_available.wait(self.message_queue.lock().await) => {
                    log::debug!(target: target::POST_OFFICE, "Message available in queue");

                    // Get the next message to send, if any
                    let message = lock.pop_front();
                    drop(lock); // Release the lock before sending, we dont want to remain locked while sending.

                    if let Some(message) = message {
                        retry_messages = Self::try_send_message(&mut self.make_client, &self.event_sink, message).await;
                    }
                }
            }

            if !retry_messages.is_empty() {
                log::debug!(target: target::POST_OFFICE, "Queueing {} messages for retry", retry_messages.len());
                for message in retry_messages {
                    self.queue_message(&message).await;
                }
            }
        }
    }

    async fn try_send_message(
        make_client: &mut F,
        event_sink: &Sender<DaemonEvent>,
        message: OutgoingMessage,
    ) -> Vec<OutgoingMessage> {
        let mut retry_messages = Vec::new();

        match (make_client)().await {
            Ok(mut client) => {
                log::debug!(target: target::POST_OFFICE, "Obtained client, sending message.");
                match client.send_message(&message).await {
                    Ok(sent_message) => {
                        log::info!(target: target::POST_OFFICE, "Message sent successfully: {}", message.guid);

                        let conversation_id = message.conversation_id.clone();
                        let event =
                            DaemonEvent::MessageSent(sent_message.into(), message, conversation_id);
                        event_sink.send(event).await.unwrap();
                    }

                    Err(e) => {
                        log::error!(target: target::POST_OFFICE, "Error sending message: {:?}", e);
                        log::warn!(target: target::POST_OFFICE, "Retrying in 5 seconds");
                        tokio::time::sleep(Duration::from_secs(5)).await;
                        retry_messages.push(message);
                    }
                }
            }

            Err(e) => {
                log::error!(target: target::POST_OFFICE, "Error creating client: {:?}", e);
                log::warn!(target: target::POST_OFFICE, "Retrying in 5 seconds");
                tokio::time::sleep(Duration::from_secs(5)).await;
                retry_messages.push(message);
            }
        }

        retry_messages
    }
}
