use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

use crate::bus::{BusError, CommittedTurnDelta, InboundMessage, MessageBus, OutboundMessage};
use crate::delivery::PresentationPolicy;
use crate::session::TurnSnapshot;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LivePolicy {
    FinalOnly,
    Snapshot { min_interval: Duration },
}

#[derive(Debug, Clone)]
pub struct TurnTarget {
    pub channel: String,
    pub chat_id: String,
    pub session_id: String,
    pub reply_to: Option<String>,
    pub metadata: HashMap<String, String>,
}

#[async_trait]
pub trait TurnSink: Send {
    async fn update(&mut self, snapshot: &TurnSnapshot) -> Result<(), ChannelError>;
    async fn finish(&mut self, snapshot: &TurnSnapshot) -> Result<(), ChannelError>;
    async fn abort(&mut self, snapshot: &TurnSnapshot) -> Result<(), ChannelError>;
}

#[derive(Debug)]
pub enum ChannelError {
    ConfigError(String),
    ConnectionError(String),
    SendError(String),
    BusError(String),
    Other(String),
}

impl std::fmt::Display for ChannelError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ChannelError::ConfigError(s) => write!(f, "Config error: {}", s),
            ChannelError::ConnectionError(s) => write!(f, "Connection error: {}", s),
            ChannelError::SendError(s) => write!(f, "Send error: {}", s),
            ChannelError::BusError(s) => write!(f, "Bus error: {}", s),
            ChannelError::Other(s) => write!(f, "Error: {}", s),
        }
    }
}

impl std::error::Error for ChannelError {}

impl ChannelError {
    pub fn is_transient(&self) -> bool {
        matches!(self, Self::ConnectionError(_) | Self::SendError(_))
    }
}

impl From<BusError> for ChannelError {
    fn from(e: BusError) -> Self {
        ChannelError::BusError(e.to_string())
    }
}

#[async_trait]
pub trait Channel: Send + Sync + 'static {
    fn name(&self) -> &str;
    fn is_running(&self) -> bool;

    /// Start the channel with a reference to the MessageBus
    async fn start(&self, bus: Arc<MessageBus>) -> Result<(), ChannelError>;

    /// Stop the channel
    async fn stop(&self) -> Result<(), ChannelError>;

    fn live_policy(&self) -> LivePolicy {
        LivePolicy::FinalOnly
    }

    fn presentation_policy(&self) -> PresentationPolicy {
        PresentationPolicy::external(matches!(self.live_policy(), LivePolicy::Snapshot { .. }))
    }

    async fn open_turn(&self, _target: TurnTarget) -> Result<Box<dyn TurnSink>, ChannelError> {
        Err(ChannelError::Other(format!(
            "channel {} does not support turn delivery",
            self.name()
        )))
    }

    /// Deliver a durable history delta after a Turn commit. Channels without
    /// local history views intentionally ignore this event.
    async fn commit_turn(
        &self,
        _target: &TurnTarget,
        _delta: CommittedTurnDelta,
    ) -> Result<(), ChannelError> {
        Ok(())
    }

    /// Whether `commit_turn` presents media references from the committed
    /// assistant message to the user. Channels that return false receive a
    /// separate media-only outbound delivery after the durable commit.
    fn commit_turn_presents_media(&self) -> bool {
        false
    }

    /// Send a message to the channel (called by OutboundDispatcher)
    async fn send(&self, msg: OutboundMessage) -> Result<(), ChannelError>;

    /// Check if a sender is allowed to use this channel
    fn is_allowed(&self, _sender_id: &str) -> bool {
        true
    }

    /// Handle an inbound message: check permissions and publish to bus
    async fn handle_and_publish(
        &self,
        bus: &Arc<MessageBus>,
        msg: &InboundMessage,
    ) -> Result<(), ChannelError> {
        if !self.is_allowed(&msg.sender_id) {
            tracing::warn!(
                channel = %self.name(),
                sender = %msg.sender_id,
                "Access denied"
            );
            return Ok(());
        }

        bus.publish_inbound(msg.clone()).await?;
        Ok(())
    }
}
