use std::collections::HashMap;

use crate::bus::{ChatMessage, MediaItem, MessageSource, OutboundMessage, SourceKind};
use crate::session::UnifiedSessionId;
use crate::tools::{OutboundDelivery, OutboundMessenger};

use super::persistence::{
    append_active_turn_message, append_persisted_message_if_absent, append_persisted_messages,
};
use super::session::{
    CURRENT_SOURCE_SESSION, CURRENT_TURN_DELIVERIES, CURRENT_TURN_ID, PendingTurnDelivery,
    SessionManager,
};

#[async_trait::async_trait]
impl OutboundMessenger for SessionManager {
    async fn send_message(
        &self,
        channel: &str,
        chat_id: &str,
        dialog_id: Option<&str>,
        content: &str,
        mut source: MessageSource,
        media: Vec<MediaItem>,
    ) -> Result<OutboundDelivery, String> {
        if source.from_session.is_none() {
            source.from_session = CURRENT_SOURCE_SESSION
                .try_with(|value| value.clone())
                .ok()
                .flatten();
        }

        let (target_sid, session) = if let Some(dialog_id) = dialog_id {
            let session_id = UnifiedSessionId::new(channel, chat_id, dialog_id);
            let session = self
                .get_or_activate_session(&session_id)
                .await
                .map_err(|error| error.to_string())?;
            (session_id, session)
        } else {
            let session_id = self
                .resolve_dialog_id(channel, chat_id)
                .await
                .map_err(|error| error.to_string())?;
            let session = self
                .get_or_create_session(&session_id)
                .await
                .map_err(|error| error.to_string())?;
            (session_id, session)
        };

        let origin = source.from_session.as_deref().unwrap_or("unknown");
        let origin_id = source.from_session.clone();
        let same_session = source.from_session.as_deref() == Some(target_sid.to_string().as_str());
        let current_turn_id = CURRENT_TURN_ID
            .try_with(|value| value.clone())
            .ok()
            .flatten();
        let current_turn_deliveries = CURRENT_TURN_DELIVERIES
            .try_with(|value| value.clone())
            .ok()
            .flatten();
        if same_session
            && channel == "cli_chat"
            && !media.is_empty()
            && let (Some(turn_id), Some(deliveries)) =
                (current_turn_id.clone(), current_turn_deliveries)
        {
            let guard = session.lock().await;
            if guard.owns_active_turn(&turn_id) {
                deliveries
                    .lock()
                    .unwrap_or_else(|poisoned| poisoned.into_inner())
                    .push(PendingTurnDelivery {
                        content: content.to_string(),
                        media,
                    });
                return Ok(OutboundDelivery::AttachedToCurrentTurn);
            }
        }
        let marked_content = if content.trim().is_empty() && !media.is_empty() && same_session {
            String::new()
        } else {
            format!("[message from {origin}] \n{content}")
        };

        let message = outbound_history_message(marked_content.clone(), source, &media);
        let message_id = message.id.clone();
        if same_session && let Some(turn_id) = current_turn_id {
            // Ownership is revalidated atomically with the in-memory append;
            // a concurrent /stop therefore falls back to a versioned write.
            append_active_turn_message(&session, message, turn_id).await
        } else {
            append_persisted_messages(&session, vec![message]).await
        }
        .map_err(|error| error.to_string())?;

        if let Some(origin_id) = origin_id {
            self.restore_origin_dialog(&origin_id, &target_sid).await;
        }

        let metadata = HashMap::from([
            ("_session_id".to_string(), target_sid.to_string()),
            ("_message_id".to_string(), message_id),
        ]);
        self.bus
            .deliver_outbound(OutboundMessage {
                channel: channel.to_string(),
                chat_id: chat_id.to_string(),
                content: marked_content,
                reply_to: None,
                media,
                metadata,
                delivery: None,
            })
            .await
            .map_err(|error| error.to_string())?;
        Ok(OutboundDelivery::Delivered)
    }
}

impl SessionManager {
    pub async fn deliver_scheduled_run(
        &self,
        run: &crate::storage::JobRun,
        delivery_owner: &str,
    ) -> Result<(), ScheduledDeliveryError> {
        let content = run
            .message
            .as_deref()
            .unwrap_or("定时任务已结束，但没有生成可投递的结果。请在任务运行记录中查看诊断信息。");
        let target_sid = if let Some(session_id) = run.target_session_id.as_deref() {
            UnifiedSessionId::parse(session_id).ok_or_else(|| {
                ScheduledDeliveryError::Permanent("stored target session is invalid".to_string())
            })?
        } else {
            let resolved = self
                .resolve_dialog_id(&run.target_channel, &run.target_chat_id)
                .await
                .map_err(|error| ScheduledDeliveryError::Transient(error.to_string()))?;
            let fixed = self
                .storage
                .set_scheduled_delivery_target_session(
                    run.id,
                    delivery_owner,
                    &resolved.to_string(),
                    chrono::Utc::now().timestamp_millis(),
                )
                .await
                .map_err(|error| ScheduledDeliveryError::Transient(error.to_string()))?
                .ok_or_else(|| {
                    ScheduledDeliveryError::Transient(
                        "scheduled delivery lost its claim before fixing target session"
                            .to_string(),
                    )
                })?;
            UnifiedSessionId::parse(&fixed).ok_or_else(|| {
                ScheduledDeliveryError::Permanent("fixed target session is invalid".to_string())
            })?
        };
        if target_sid.channel != run.target_channel || target_sid.chat_id != run.target_chat_id {
            return Err(ScheduledDeliveryError::Permanent(
                "fixed target session does not belong to the scheduled destination".to_string(),
            ));
        }
        let session = self
            .get_or_activate_session(&target_sid)
            .await
            .map_err(|error| ScheduledDeliveryError::Transient(error.to_string()))?;
        let source = MessageSource {
            kind: SourceKind::ExternalTrigger,
            from_channel: Some("scheduler".to_string()),
            from_session: Some(format!("scheduled-run:{}", run.id)),
            from_user_id: None,
            system_name: Some("scheduled task".to_string()),
            task_id: Some(run.job_id.clone()),
            from_run_id: run.agent_run_id.clone(),
            from_agent_id: run.agent_id.clone(),
        };
        let mut message = outbound_history_message(content, source, &[]);
        message.id = format!("scheduled:{}", run.id);
        let message_id = message.id.clone();
        append_persisted_message_if_absent(&session, message)
            .await
            .map_err(|error| ScheduledDeliveryError::Transient(error.to_string()))?;

        let metadata = HashMap::from([
            ("_session_id".to_string(), target_sid.to_string()),
            ("_message_id".to_string(), message_id),
            ("scheduled_delivery_id".to_string(), run.id.to_string()),
        ]);
        self.bus
            .deliver_outbound(OutboundMessage {
                channel: run.target_channel.clone(),
                chat_id: run.target_chat_id.clone(),
                content: content.to_string(),
                reply_to: None,
                media: Vec::new(),
                metadata,
                delivery: None,
            })
            .await
            .map_err(|error| match error {
                crate::bus::BusError::Closed
                | crate::bus::BusError::DeliveryTimedOut
                | crate::bus::BusError::DeliveryTransient(_) => {
                    ScheduledDeliveryError::Transient(error.to_string())
                }
                crate::bus::BusError::DeliveryPermanent(summary) => {
                    ScheduledDeliveryError::Permanent(summary)
                }
            })?;
        Ok(())
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ScheduledDeliveryError {
    #[error("transient scheduled delivery failure: {0}")]
    Transient(String),
    #[error("permanent scheduled delivery failure: {0}")]
    Permanent(String),
}

fn outbound_history_message(
    content: impl Into<String>,
    source: MessageSource,
    media: &[MediaItem],
) -> ChatMessage {
    let mut message = ChatMessage::assistant_with_source(content, source);
    message.media_refs = media.iter().map(MediaItem::to_media_ref).collect();
    message
}

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

    #[test]
    fn outbound_history_preserves_delivered_media() {
        let source = MessageSource {
            kind: SourceKind::CrossChannel,
            from_channel: Some("cli_chat".into()),
            from_session: Some("cli_chat:source:dialog".into()),
            from_user_id: None,
            system_name: None,
            task_id: None,
            from_run_id: None,
            from_agent_id: None,
        };
        let media = vec![MediaItem::new("/tmp/report.pdf", "file")];

        let message = outbound_history_message("report", source, &media);

        assert_eq!(message.media_refs.len(), 1);
        assert_eq!(message.media_refs[0].path, "/tmp/report.pdf");
        assert_eq!(message.media_refs[0].media_type, "file");
    }
}
