use crate::channels::ChannelMessage;
use crate::gateway::{channel_session_id, AppTurnRequest, GatewayEntryContext, TurnMention};
use thiserror::Error;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GatewayChannelMention {
    pub id: String,
    pub display_name: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GatewayChannelMessage {
    pub channel: String,
    pub channel_instance_id: Option<String>,
    pub conversation_id: String,
    pub sender_id: String,
    pub agent_preset_id: Option<String>,
    pub message_id: String,
    pub text: String,
    pub channel_identity_prompt: Option<String>,
    pub reply_to_message_id: Option<String>,
    pub root_message_id: Option<String>,
    pub mentions: Vec<GatewayChannelMention>,
}

#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum GatewayChannelIngressError {
    #[error("gateway channel ingress does not yet support channel attachments")]
    UnsupportedAttachments,
}

pub fn build_gateway_channel_message(
    message: ChannelMessage,
) -> Result<GatewayChannelMessage, GatewayChannelIngressError> {
    let ChannelMessage {
        channel,
        channel_instance_id,
        conversation_id,
        sender_id,
        message_id,
        text,
        reply_to_message_id,
        root_message_id,
        mentions,
        attachments,
    } = message;

    if !attachments.is_empty() {
        return Err(GatewayChannelIngressError::UnsupportedAttachments);
    }

    Ok(GatewayChannelMessage {
        channel,
        channel_instance_id,
        conversation_id,
        sender_id,
        agent_preset_id: None,
        message_id,
        text,
        channel_identity_prompt: None,
        reply_to_message_id,
        root_message_id,
        mentions: mentions
            .into_iter()
            .map(|mention| GatewayChannelMention {
                id: mention.id,
                display_name: mention.display_name,
            })
            .collect(),
    })
}

pub fn build_channel_turn_request(message: &GatewayChannelMessage) -> AppTurnRequest {
    AppTurnRequest {
        session_id: channel_session_id(
            &message.channel,
            message.channel_instance_id.as_deref(),
            &message.conversation_id,
        ),
        entry: GatewayEntryContext {
            runtime_profile_id: message.agent_preset_id.clone(),
            ..GatewayEntryContext::channel(message.channel_instance_id.clone())
        },
        channel: Some(message.channel.clone()),
        message_id: Some(message.message_id.clone()),
        conversation_id: message.conversation_id.clone(),
        sender_id: message.sender_id.clone(),
        text: message.text.clone(),
        channel_instance_id: message.channel_instance_id.clone(),
        channel_identity_prompt: message.channel_identity_prompt.clone(),
        reply_to_message_id: message.reply_to_message_id.clone(),
        root_message_id: message.root_message_id.clone(),
        mentions: message
            .mentions
            .iter()
            .map(|mention| TurnMention {
                id: mention.id.clone(),
                display_name: mention.display_name.clone(),
            })
            .collect(),
        reasoning_effort: Default::default(),
    }
}

#[cfg(test)]
mod tests {
    use super::{
        build_channel_turn_request, build_gateway_channel_message, GatewayChannelIngressError,
        GatewayChannelMention, GatewayChannelMessage,
    };
    use crate::channels::{ChannelAttachment, ChannelMention, ChannelMessage};

    #[test]
    fn builds_channel_turn_request_with_instance_scoped_session_id() {
        let message = GatewayChannelMessage {
            channel: "feishu".to_string(),
            channel_instance_id: Some("ops-feishu".to_string()),
            conversation_id: "conv-1".to_string(),
            sender_id: "user-1".to_string(),
            agent_preset_id: Some("code-reviewer".to_string()),
            message_id: "msg-1".to_string(),
            text: "ping".to_string(),
            channel_identity_prompt: Some("<participant_directory />".to_string()),
            reply_to_message_id: Some("prev-1".to_string()),
            root_message_id: Some("root-1".to_string()),
            mentions: vec![GatewayChannelMention {
                id: "bot".to_string(),
                display_name: Some("XiaoO".to_string()),
            }],
        };

        let request = build_channel_turn_request(&message);

        assert_eq!(request.session_id, "ops-feishu:conv-1");
        assert_eq!(request.entry.instance_id.as_deref(), Some("ops-feishu"));
        assert_eq!(
            request.entry.runtime_profile_id.as_deref(),
            Some("code-reviewer")
        );
        assert_eq!(request.channel.as_deref(), Some("feishu"));
        assert_eq!(request.message_id.as_deref(), Some("msg-1"));
        assert_eq!(
            request.channel_identity_prompt.as_deref(),
            Some("<participant_directory />")
        );
        assert_eq!(request.mentions.len(), 1);
        assert_eq!(request.mentions[0].id, "bot");
    }

    #[test]
    fn falls_back_to_channel_name_when_instance_id_is_absent() {
        let message = GatewayChannelMessage {
            channel: "dingtalk".to_string(),
            channel_instance_id: None,
            conversation_id: "conv-2".to_string(),
            sender_id: "user-2".to_string(),
            agent_preset_id: None,
            message_id: "msg-2".to_string(),
            text: "hello".to_string(),
            channel_identity_prompt: None,
            reply_to_message_id: None,
            root_message_id: None,
            mentions: Vec::new(),
        };

        let request = build_channel_turn_request(&message);

        assert_eq!(request.session_id, "dingtalk:conv-2");
        assert_eq!(request.entry.instance_id, None);
    }

    #[test]
    fn converts_channel_message_without_attachments() {
        let message = ChannelMessage {
            channel: "feishu".to_string(),
            channel_instance_id: Some("ops-feishu".to_string()),
            conversation_id: "conv-3".to_string(),
            sender_id: "user-3".to_string(),
            message_id: "msg-3".to_string(),
            text: "hello".to_string(),
            reply_to_message_id: None,
            root_message_id: None,
            mentions: vec![ChannelMention {
                id: "bot".to_string(),
                display_name: Some("XiaoO".to_string()),
            }],
            attachments: Vec::new(),
        };

        let gateway_message =
            build_gateway_channel_message(message).expect("message should convert");

        assert_eq!(gateway_message.channel, "feishu");
        assert_eq!(
            gateway_message.channel_instance_id.as_deref(),
            Some("ops-feishu")
        );
        assert!(gateway_message.channel_identity_prompt.is_none());
        assert_eq!(gateway_message.mentions.len(), 1);
    }

    #[test]
    fn rejects_channel_message_with_attachments() {
        let message = ChannelMessage {
            channel: "feishu".to_string(),
            channel_instance_id: None,
            conversation_id: "conv-4".to_string(),
            sender_id: "user-4".to_string(),
            message_id: "msg-4".to_string(),
            text: "hello".to_string(),
            reply_to_message_id: None,
            root_message_id: None,
            mentions: Vec::new(),
            attachments: vec![ChannelAttachment {
                kind: "file".to_string(),
                file_name: "demo.txt".to_string(),
                bytes: b"demo".to_vec(),
            }],
        };

        let error =
            build_gateway_channel_message(message).expect_err("attachments should fail fast");

        assert_eq!(error, GatewayChannelIngressError::UnsupportedAttachments);
    }
}