use std::collections::HashMap;
use std::sync::Arc;

use tokio::sync::RwLock;

use crate::bus::{MessageBus, OutboundMessage};
use crate::channels::base::{Channel, ChannelError};

/// Consumes outbound messages from MessageBus and dispatches them to channels.
pub struct OutboundDispatcher {
    bus: Arc<MessageBus>,
    channels: Arc<RwLock<HashMap<String, Arc<dyn Channel + Send + Sync>>>>,
}

/// Prefix for virtual scheduler chat IDs that should not be sent to external channels.
const SCHEDULER_VIRTUAL_CHAT_ID_PREFIX: &str = "scheduler/";

impl OutboundDispatcher {
    pub fn new(bus: Arc<MessageBus>) -> Self {
        Self {
            bus,
            channels: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    pub async fn register_channel(&self, name: &str, channel: Arc<dyn Channel + Send + Sync>) {
        self.channels
            .write()
            .await
            .insert(name.to_string(), channel);
    }

    pub async fn run(&self) {
        tracing::info!("OutboundDispatcher started");

        loop {
            let msg = match self.bus.consume_outbound().await {
                Some(msg) => msg,
                None => {
                    tracing::info!("Outbound bus closed, stopping dispatcher");
                    break;
                }
            };
            #[cfg(debug_assertions)]
            tracing::debug!(
                channel = %msg.channel,
                chat_id = %msg.chat_id,
                content_len = msg.content.len(),
                "OutboundDispatcher received message"
            );

            // Skip messages with virtual scheduler chat IDs (e.g., "scheduler/job_id")
            // These are internal messages from SilentAgentTask that should not be sent externally
            if msg.chat_id.starts_with(SCHEDULER_VIRTUAL_CHAT_ID_PREFIX) {
                tracing::debug!(
                    channel = %msg.channel,
                    chat_id = %msg.chat_id,
                    "Skipping message with virtual scheduler chat_id"
                );
                continue;
            }

            let channel_name = msg.channel.clone();
            let channel = self.channels.read().await.get(&channel_name).cloned();

            match channel {
                Some(ch) => {
                    if let Err(error) = self.send_with_retry(&*ch, msg).await {
                        tracing::error!(channel = %channel_name, error = %error, "Failed to send message after retries");
                    }
                }
                None => {
                    tracing::warn!(channel = %channel_name, "No channel found for message");
                }
            }
        }
    }

    async fn send_with_retry(
        &self,
        channel: &dyn Channel,
        msg: OutboundMessage,
    ) -> Result<(), ChannelError> {
        const DELAYS: [u64; 3] = [1, 2, 4];

        for (attempt_index, delay) in DELAYS.iter().enumerate() {
            match channel.send(msg.clone()).await {
                Ok(()) => return Ok(()),
                Err(error) if attempt_index < DELAYS.len() - 1 => {
                    tracing::warn!(
                        attempt = attempt_index + 1,
                        delay = delay,
                        error = %error,
                        "Send failed, retrying"
                    );
                    tokio::time::sleep(tokio::time::Duration::from_secs(*delay)).await;
                }
                Err(error) => return Err(error),
            }
        }

        unreachable!()
    }
}
