use std::sync::Arc;

use tokio::sync::{oneshot, watch};

use crate::bus::CommittedTurnDelta;
use crate::channels::{ChannelManager, TurnTarget};
use crate::delivery::{DeliveryCoordinator, DeliveryError};
use crate::session::TurnSnapshot;
use crate::task_supervisor::TaskSupervisor;

use super::coordinator::SinkRoute;

/// Gateway-owned facade that resolves a target Channel and starts exactly one
/// TurnSink lifecycle. Session workers depend on this abstraction rather than
/// on Channel implementations or WebSocket/Feishu protocols.
#[derive(Clone)]
pub struct TurnDeliveryService {
    coordinator: DeliveryCoordinator,
    channels: ChannelManager,
    supervisor: TaskSupervisor,
}

/// Completion handle for one TurnSink lifecycle.
///
/// Creating a sink only proves that delivery started. Callers consume this
/// handle after publishing a terminal snapshot to learn whether the terminal
/// write actually reached the Channel.
pub struct TurnDeliveryHandle {
    pub(crate) completion: oneshot::Receiver<Result<(), DeliveryError>>,
}

impl TurnDeliveryHandle {
    pub async fn wait(self) -> Result<(), DeliveryError> {
        self.completion
            .await
            .unwrap_or(Err(DeliveryError::CompletionLost))
    }
}

impl TurnDeliveryService {
    pub fn new(
        coordinator: DeliveryCoordinator,
        channels: ChannelManager,
        supervisor: TaskSupervisor,
    ) -> Self {
        Self {
            coordinator,
            channels,
            supervisor,
        }
    }

    pub async fn start(
        &self,
        target: TurnTarget,
        snapshots: watch::Receiver<Arc<TurnSnapshot>>,
    ) -> Result<TurnDeliveryHandle, DeliveryError> {
        let channel = self
            .channels
            .get_channel(&target.channel)
            .await
            .ok_or_else(|| DeliveryError::ChannelNotFound(target.channel.clone()))?;
        let live_policy = channel.live_policy();
        let presentation = channel.presentation_policy();
        let sink = channel
            .open_turn(target.clone())
            .await
            .map_err(DeliveryError::OpenFailed)?;
        let completion = self.coordinator.spawn_sink(
            &self.supervisor,
            SinkRoute {
                channel: target.channel,
                chat_id: target.chat_id,
                live_policy,
                presentation,
            },
            snapshots,
            sink,
        )?;
        Ok(TurnDeliveryHandle { completion })
    }

    pub async fn commit(
        &self,
        target: &TurnTarget,
        delta: CommittedTurnDelta,
    ) -> Result<bool, DeliveryError> {
        let channel = self
            .channels
            .get_channel(&target.channel)
            .await
            .ok_or_else(|| DeliveryError::ChannelNotFound(target.channel.clone()))?;
        let presents_media = channel.commit_turn_presents_media();
        channel
            .commit_turn(target, delta)
            .await
            .map_err(DeliveryError::FinalFailed)?;
        Ok(presents_media)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn handle_reports_terminal_delivery_failure() {
        let (sender, completion) = oneshot::channel();
        sender.send(Err(DeliveryError::FinalTimedOut)).unwrap();

        let result = TurnDeliveryHandle { completion }.wait().await;
        assert!(matches!(result, Err(DeliveryError::FinalTimedOut)));
    }

    #[tokio::test]
    async fn handle_reports_lost_delivery_task() {
        let (sender, completion) = oneshot::channel();
        drop(sender);

        let result = TurnDeliveryHandle { completion }.wait().await;
        assert!(matches!(result, Err(DeliveryError::CompletionLost)));
    }
}
