use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;

use tokio::sync::mpsc;

use crate::bus::{DeliveryReceipt, MessageBus, OutboundMessage};
use crate::channels::ChannelManager;
use crate::channels::base::{Channel, ChannelError};
use crate::delivery::ConversationWriteLocks;
use crate::task_supervisor::TaskSupervisor;

const LANE_CAPACITY: usize = 64;
const LANE_IDLE_TIMEOUT: Duration = Duration::from_secs(300);
const SEND_TIMEOUT: Duration = Duration::from_secs(30);

/// Dispatches outbound messages through independent per-conversation lanes.
/// Messages to the same channel/chat remain ordered, while a slow destination
/// cannot block delivery to unrelated conversations.
pub struct OutboundDispatcher {
    bus: Arc<MessageBus>,
    channel_manager: ChannelManager,
    task_supervisor: TaskSupervisor,
    write_locks: ConversationWriteLocks,
    active_lanes: Arc<AtomicUsize>,
}

impl OutboundDispatcher {
    pub fn new(
        bus: Arc<MessageBus>,
        channel_manager: ChannelManager,
        task_supervisor: TaskSupervisor,
        write_locks: ConversationWriteLocks,
        active_lanes: Arc<AtomicUsize>,
    ) -> Self {
        Self {
            bus,
            channel_manager,
            task_supervisor,
            write_locks,
            active_lanes,
        }
    }

    pub async fn run(&self) {
        tracing::info!(lane_capacity = LANE_CAPACITY, "OutboundDispatcher started");
        let mut lanes: HashMap<String, mpsc::Sender<OutboundMessage>> = HashMap::new();
        let mut messages_seen = 0_u64;

        loop {
            let Some(msg) = self.bus.consume_outbound().await else {
                tracing::warn!("OutboundDispatcher stopping because outbound bus closed");
                break;
            };

            messages_seen = messages_seen.wrapping_add(1);
            if messages_seen.is_multiple_of(128) {
                lanes.retain(|_, sender| !sender.is_closed());
            }

            let lane_key = format!("{}\0{}", msg.channel, msg.chat_id);
            let mut sender = lanes.get(&lane_key).cloned();
            if sender.as_ref().is_none_or(mpsc::Sender::is_closed) {
                let Some(channel) = self.channel_manager.get_channel(&msg.channel).await else {
                    tracing::warn!(channel = %msg.channel, "No channel found for message");
                    msg.complete_delivery(DeliveryReceipt::PermanentFailure {
                        summary: format!("channel not found: {}", msg.channel),
                    });
                    continue;
                };
                let (new_sender, receiver) = mpsc::channel(LANE_CAPACITY);
                if !self.spawn_lane(channel, receiver, msg.channel.clone(), msg.chat_id.clone()) {
                    msg.complete_delivery(DeliveryReceipt::DispatcherClosed);
                    continue;
                }
                lanes.insert(lane_key.clone(), new_sender.clone());
                sender = Some(new_sender);
            }

            let Some(sender) = sender else {
                tracing::error!("Outbound lane creation did not produce a sender");
                continue;
            };
            match sender.try_send(msg) {
                Ok(()) => {}
                Err(mpsc::error::TrySendError::Full(msg)) => {
                    tracing::error!(
                        channel = %msg.channel,
                        chat_id = %msg.chat_id,
                        capacity = LANE_CAPACITY,
                        "Outbound lane full; rejecting message instead of blocking other destinations"
                    );
                    msg.complete_delivery(DeliveryReceipt::TransientFailure {
                        summary: "outbound lane is full".to_string(),
                    });
                }
                Err(mpsc::error::TrySendError::Closed(msg)) => {
                    // The lane may have expired between the closed check and
                    // enqueue. Recreate it once and preserve this message.
                    lanes.remove(&lane_key);
                    let Some(channel) = self.channel_manager.get_channel(&msg.channel).await else {
                        tracing::warn!(channel = %msg.channel, "No channel found for message");
                        msg.complete_delivery(DeliveryReceipt::PermanentFailure {
                            summary: format!("channel not found: {}", msg.channel),
                        });
                        continue;
                    };
                    let (new_sender, receiver) = mpsc::channel(LANE_CAPACITY);
                    if !self.spawn_lane(channel, receiver, msg.channel.clone(), msg.chat_id.clone())
                    {
                        msg.complete_delivery(DeliveryReceipt::DispatcherClosed);
                        continue;
                    }
                    match new_sender.try_send(msg) {
                        Ok(()) => {
                            lanes.insert(lane_key, new_sender);
                        }
                        Err(error) => {
                            error
                                .into_inner()
                                .complete_delivery(DeliveryReceipt::DispatcherClosed);
                        }
                    }
                }
            }
        }
    }

    fn spawn_lane(
        &self,
        channel: Arc<dyn Channel + Send + Sync>,
        mut receiver: mpsc::Receiver<OutboundMessage>,
        channel_name: String,
        chat_id: String,
    ) -> bool {
        let target_lock = self.write_locks.for_target(&channel_name, &chat_id);
        let active_lanes = self.active_lanes.clone();
        self.task_supervisor.spawn(
            format!("outbound-lane:{channel_name}:{chat_id}"),
            async move {
                active_lanes.fetch_add(1, Ordering::Relaxed);
                let _guard = LaneGuard {
                    counter: active_lanes,
                };
                loop {
                    let msg = match tokio::time::timeout(LANE_IDLE_TIMEOUT, receiver.recv()).await {
                        Ok(Some(msg)) => msg,
                        Ok(None) | Err(_) => break,
                    };
                    let result = Self::send_with_retry(&*channel, &msg, &target_lock).await;
                    if result != DeliveryReceipt::Delivered {
                        tracing::error!(
                            channel = %channel_name,
                            chat_id = %chat_id,
                            result = ?result,
                            "Failed to send message after retries"
                        );
                    }
                    msg.complete_delivery(result);
                }
            },
        )
    }

    async fn send_with_retry(
        channel: &dyn Channel,
        msg: &OutboundMessage,
        target_lock: &tokio::sync::Mutex<()>,
    ) -> DeliveryReceipt {
        let _guard = target_lock.lock().await;
        const DELAYS: &[u64] = &[1, 2, 4];

        for (attempt, &delay) in DELAYS.iter().enumerate() {
            let result = tokio::time::timeout(SEND_TIMEOUT, channel.send(msg.clone())).await;
            match result {
                Ok(Ok(())) => return DeliveryReceipt::Delivered,
                Ok(Err(error)) if attempt < DELAYS.len() - 1 && error.is_transient() => {
                    tracing::warn!(
                        attempt = attempt + 1,
                        delay,
                        error_class = channel_error_class(&error),
                        "Send failed, retrying"
                    );
                }
                Ok(Err(error)) => return receipt_from_channel_error(error),
                Err(_) if attempt < DELAYS.len() - 1 => {
                    tracing::warn!(attempt = attempt + 1, delay, "Send timed out, retrying");
                }
                Err(_) => {
                    return DeliveryReceipt::TimedOut;
                }
            }
            tokio::time::sleep(Duration::from_secs(delay)).await;
        }
        unreachable!()
    }
}

fn channel_error_class(error: &ChannelError) -> &'static str {
    match error {
        ChannelError::ConnectionError(_) => "connection",
        ChannelError::SendError(_) => "send",
        ChannelError::BusError(_) => "bus",
        ChannelError::ConfigError(_) => "config",
        ChannelError::Other(_) => "other",
    }
}

fn receipt_from_channel_error(error: ChannelError) -> DeliveryReceipt {
    match error {
        ChannelError::ConnectionError(_) => DeliveryReceipt::TransientFailure {
            summary: "channel connection failed after retries".to_string(),
        },
        ChannelError::SendError(_) => DeliveryReceipt::TransientFailure {
            summary: "channel send failed after retries".to_string(),
        },
        ChannelError::BusError(_) => DeliveryReceipt::TransientFailure {
            summary: "channel bus was unavailable".to_string(),
        },
        ChannelError::ConfigError(_) => DeliveryReceipt::PermanentFailure {
            summary: "channel configuration rejected delivery".to_string(),
        },
        ChannelError::Other(_) => DeliveryReceipt::PermanentFailure {
            summary: "channel rejected delivery".to_string(),
        },
    }
}

/// Decrements the active-lane counter exactly once when a lane task ends,
/// whether it exits normally, is cancelled, or is aborted.
struct LaneGuard {
    counter: Arc<AtomicUsize>,
}

impl Drop for LaneGuard {
    fn drop(&mut self) {
        self.counter.fetch_sub(1, Ordering::Relaxed);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use async_trait::async_trait;
    use std::sync::atomic::{AtomicUsize, Ordering};
    use tokio::sync::{Mutex, Notify};

    struct RecordingChannel {
        sent: Mutex<Vec<String>>,
        notify: Notify,
    }

    struct PermanentFailureChannel {
        attempts: AtomicUsize,
    }

    #[async_trait]
    impl Channel for PermanentFailureChannel {
        fn name(&self) -> &str {
            "permanent-failure"
        }
        fn is_running(&self) -> bool {
            true
        }
        async fn start(&self, _bus: Arc<MessageBus>) -> Result<(), ChannelError> {
            Ok(())
        }
        async fn stop(&self) -> Result<(), ChannelError> {
            Ok(())
        }
        async fn send(&self, _msg: OutboundMessage) -> Result<(), ChannelError> {
            self.attempts.fetch_add(1, Ordering::SeqCst);
            Err(ChannelError::Other("invalid destination".to_string()))
        }
    }

    #[async_trait]
    impl Channel for RecordingChannel {
        fn name(&self) -> &str {
            "recording"
        }

        fn is_running(&self) -> bool {
            true
        }

        async fn start(&self, _bus: Arc<MessageBus>) -> Result<(), ChannelError> {
            Ok(())
        }

        async fn stop(&self) -> Result<(), ChannelError> {
            Ok(())
        }

        async fn send(&self, msg: OutboundMessage) -> Result<(), ChannelError> {
            if msg.chat_id == "slow" {
                tokio::time::sleep(Duration::from_millis(50)).await;
            }
            self.sent.lock().await.push(msg.content);
            self.notify.notify_waiters();
            Ok(())
        }
    }

    fn outbound(chat_id: &str, content: &str) -> OutboundMessage {
        OutboundMessage {
            channel: "recording".to_string(),
            chat_id: chat_id.to_string(),
            content: content.to_string(),
            reply_to: None,
            media: vec![],
            metadata: HashMap::new(),
            delivery: None,
        }
    }

    #[tokio::test]
    async fn slow_conversation_does_not_block_other_conversations() {
        let bus = MessageBus::new(8);
        let manager = ChannelManager::with_bus(
            Arc::new(crate::channels::CliChatChannel::new()),
            bus.clone(),
        );
        let channel = Arc::new(RecordingChannel {
            sent: Mutex::new(Vec::new()),
            notify: Notify::new(),
        });
        manager.register_channel("recording", channel.clone()).await;

        let supervisor = TaskSupervisor::new();
        let dispatcher = OutboundDispatcher::new(
            bus.clone(),
            manager,
            supervisor.clone(),
            ConversationWriteLocks::default(),
            Arc::new(AtomicUsize::new(0)),
        );
        let task = tokio::spawn(async move { dispatcher.run().await });
        bus.publish_outbound(outbound("slow", "slow-1"))
            .await
            .unwrap();
        bus.publish_outbound(outbound("slow", "slow-2"))
            .await
            .unwrap();
        bus.publish_outbound(outbound("fast", "fast-1"))
            .await
            .unwrap();

        tokio::time::timeout(Duration::from_secs(1), async {
            loop {
                if channel.sent.lock().await.len() == 3 {
                    break;
                }
                tokio::time::sleep(Duration::from_millis(5)).await;
            }
        })
        .await
        .unwrap();

        let sent = channel.sent.lock().await.clone();
        assert_eq!(sent[0], "fast-1");
        assert_eq!(&sent[1..], &["slow-1", "slow-2"]);
        task.abort();
        supervisor.shutdown(Duration::from_secs(1)).await;
    }

    #[tokio::test]
    async fn confirmed_delivery_reports_missing_channel() {
        let bus = MessageBus::new(8);
        let manager = ChannelManager::with_bus(
            Arc::new(crate::channels::CliChatChannel::new()),
            bus.clone(),
        );
        let supervisor = TaskSupervisor::new();
        let dispatcher = OutboundDispatcher::new(
            bus.clone(),
            manager,
            supervisor.clone(),
            ConversationWriteLocks::default(),
            Arc::new(AtomicUsize::new(0)),
        );
        let task = tokio::spawn(async move { dispatcher.run().await });

        let mut message = outbound("missing", "not delivered");
        message.channel = "missing".to_string();
        let error = bus.deliver_outbound(message).await.unwrap_err();

        assert!(matches!(error, crate::bus::BusError::DeliveryPermanent(_)));
        task.abort();
        supervisor.shutdown(Duration::from_secs(1)).await;
    }

    #[tokio::test]
    async fn confirmed_delivery_waits_for_channel_send() {
        let bus = MessageBus::new(8);
        let manager = ChannelManager::with_bus(
            Arc::new(crate::channels::CliChatChannel::new()),
            bus.clone(),
        );
        let channel = Arc::new(RecordingChannel {
            sent: Mutex::new(Vec::new()),
            notify: Notify::new(),
        });
        manager.register_channel("recording", channel.clone()).await;
        let supervisor = TaskSupervisor::new();
        let dispatcher = OutboundDispatcher::new(
            bus.clone(),
            manager,
            supervisor.clone(),
            ConversationWriteLocks::default(),
            Arc::new(AtomicUsize::new(0)),
        );
        let task = tokio::spawn(async move { dispatcher.run().await });

        bus.deliver_outbound(outbound("confirmed", "delivered"))
            .await
            .unwrap();

        assert_eq!(channel.sent.lock().await.as_slice(), &["delivered"]);
        task.abort();
        supervisor.shutdown(Duration::from_secs(1)).await;
    }

    #[tokio::test]
    async fn active_lane_count_tracks_task_lifetime() {
        let bus = MessageBus::new(8);
        let manager = ChannelManager::with_bus(
            Arc::new(crate::channels::CliChatChannel::new()),
            bus.clone(),
        );
        manager
            .register_channel(
                "recording",
                Arc::new(RecordingChannel {
                    sent: Mutex::new(Vec::new()),
                    notify: Notify::new(),
                }),
            )
            .await;
        let supervisor = TaskSupervisor::new();
        let active_lanes = Arc::new(AtomicUsize::new(0));
        let dispatcher = OutboundDispatcher::new(
            bus.clone(),
            manager,
            supervisor.clone(),
            ConversationWriteLocks::default(),
            active_lanes.clone(),
        );
        let dispatcher_task = tokio::spawn(async move { dispatcher.run().await });

        bus.publish_outbound(outbound("counted", "message"))
            .await
            .unwrap();
        tokio::time::timeout(Duration::from_secs(1), async {
            while active_lanes.load(Ordering::Relaxed) != 1 {
                tokio::task::yield_now().await;
            }
        })
        .await
        .unwrap();

        supervisor.shutdown(Duration::from_secs(1)).await;
        assert_eq!(active_lanes.load(Ordering::Relaxed), 0);
        dispatcher_task.abort();
        let _ = dispatcher_task.await;
    }

    #[tokio::test]
    async fn permanent_send_failure_is_not_retried() {
        let channel = PermanentFailureChannel {
            attempts: AtomicUsize::new(0),
        };

        let target_lock = tokio::sync::Mutex::new(());
        let receipt = OutboundDispatcher::send_with_retry(
            &channel,
            &outbound("invalid", "message"),
            &target_lock,
        )
        .await;

        assert!(matches!(receipt, DeliveryReceipt::PermanentFailure { .. }));
        assert_eq!(channel.attempts.load(Ordering::SeqCst), 1);
    }

    #[test]
    fn channel_errors_map_to_typed_sanitized_receipts() {
        for error in [
            ChannelError::ConnectionError("https://secret.example/?token=x".to_string()),
            ChannelError::SendError("private response body".to_string()),
            ChannelError::BusError("private queue detail".to_string()),
        ] {
            let receipt = receipt_from_channel_error(error);
            assert!(matches!(receipt, DeliveryReceipt::TransientFailure { .. }));
            assert!(!format!("{receipt:?}").contains("private"));
            assert!(!format!("{receipt:?}").contains("secret"));
        }
        for error in [
            ChannelError::ConfigError("api_key=x".to_string()),
            ChannelError::Other("private platform payload".to_string()),
        ] {
            let receipt = receipt_from_channel_error(error);
            assert!(matches!(receipt, DeliveryReceipt::PermanentFailure { .. }));
            assert!(!format!("{receipt:?}").contains("private"));
            assert!(!format!("{receipt:?}").contains("api_key"));
        }
    }

    #[tokio::test]
    async fn shared_write_lock_orders_dispatcher_with_live_turn_writes() {
        let channel = RecordingChannel {
            sent: Mutex::new(Vec::new()),
            notify: Notify::new(),
        };
        let write_locks = ConversationWriteLocks::default();
        let target_lock = write_locks.for_target("recording", "same-chat");
        let live_write = target_lock.lock().await;
        let message = outbound("same-chat", "after-live-update");

        let send = OutboundDispatcher::send_with_retry(&channel, &message, &target_lock);
        tokio::pin!(send);
        assert!(
            tokio::time::timeout(Duration::from_millis(10), &mut send)
                .await
                .is_err()
        );
        assert!(channel.sent.lock().await.is_empty());

        drop(live_write);
        assert_eq!(send.await, DeliveryReceipt::Delivered);
        assert_eq!(channel.sent.lock().await.as_slice(), &["after-live-update"]);
    }
}
