use std::collections::HashSet;
use std::sync::Arc;

use async_trait::async_trait;
use mime_guess::mime;

use crate::bus::{MediaItem, MessageSource, SourceKind};

use super::traits::{OutboundDelivery, OutboundMessenger, Tool, ToolResult};

pub struct SendMessageTool {
    messenger: Arc<dyn OutboundMessenger>,
    available_channels: HashSet<String>,
}

impl SendMessageTool {
    pub fn new(messenger: Arc<dyn OutboundMessenger>, available_channels: Vec<String>) -> Self {
        Self {
            messenger,
            available_channels: available_channels.into_iter().collect(),
        }
    }
}

/// Parse target_chat_id into (channel, chat_id, optional dialog_id).
/// Accepts two formats:
///   - Two-part:  `<channel>:<chat_id>` → sends to latest active session for that chat
///   - Three-part: `<channel>:<chat_id>:<dialog_id>` → sends to specific session
fn parse_target_chat_id(raw: &str) -> Result<(&str, &str, Option<&str>), String> {
    let parts: Vec<&str> = raw.split(':').collect();
    match parts.len() {
        2 => {
            if parts[0].is_empty() || parts[1].is_empty() {
                Err(format!(
                    "Invalid target_chat_id format '{}': channel and chat_id must not be empty",
                    raw
                ))
            } else {
                Ok((parts[0], parts[1], None))
            }
        }
        3 => {
            if parts[0].is_empty() || parts[1].is_empty() || parts[2].is_empty() {
                Err(format!(
                    "Invalid target_chat_id format '{}': all three parts must not be empty",
                    raw
                ))
            } else {
                Ok((parts[0], parts[1], Some(parts[2])))
            }
        }
        _ => Err(format!(
            "Invalid target_chat_id format '{}'. Expected <channel>:<chat_id> or <channel>:<chat_id>:<dialog_id>",
            raw
        )),
    }
}

#[async_trait]
impl Tool for SendMessageTool {
    fn name(&self) -> &str {
        "send_message"
    }

    fn description(&self) -> &str {
        "向指定渠道的会话发送消息，可附带文件。用于在用户请求下向其他渠道发送内容。\
target_chat_id 支持两种格式：<channel>:<chat_id>（发送到该聊天下最新活跃会话）\
或 <channel>:<chat_id>:<dialog_id>（发送到指定会话，过期则自动激活）。\
如需发送文件，使用 files 参数指定文件路径列表。"
    }

    fn parameters_schema(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "target_chat_id": {
                    "type": "string",
                    "description": "目标会话ID。支持两种格式: 1) <channel>:<chat_id> 发送到该聊天下最新活跃会话, 无则自动创建; 2) <channel>:<chat_id>:<dialog_id> 发送到指定会话, 过期则自动激活。channel 可选值: feishu, cli_chat"
                },
                "content": {
                    "type": "string",
                    "description": "要发送的消息内容"
                },
                "origin": {
                    "type": "string",
                    "description": "可选。消息来源标识。不填则自动使用当前会话的完整 session_id (<channel>:<chat_id>:<dialog_id>)"
                },
                "files": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "可选。要发送的文件路径列表，支持绝对路径和工作区相对路径"
                }
            },
            "required": ["target_chat_id", "content"]
        })
    }

    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
        let raw_id = args["target_chat_id"]
            .as_str()
            .ok_or_else(|| anyhow::anyhow!("missing target_chat_id"))?;
        let content = args["content"]
            .as_str()
            .ok_or_else(|| anyhow::anyhow!("missing content"))?;

        // 1. Parse target_chat_id
        let (channel, chat_id, dialog_id) =
            parse_target_chat_id(raw_id).map_err(|e| anyhow::anyhow!(e))?;

        // 2. Validate channel
        if !self.available_channels.contains(channel) {
            return Ok(ToolResult {
                success: false,
                output: String::new(),
                error: Some(format!(
                    "Channel '{}' is not available. Available channels: {}",
                    channel,
                    self.available_channels
                        .iter()
                        .cloned()
                        .collect::<Vec<_>>()
                        .join(", ")
                )),
            });
        }

        let from_session = args["origin"].as_str().map(|s| s.to_string());

        let source = MessageSource {
            kind: SourceKind::CrossChannel,
            from_channel: Some("tool".to_string()),
            from_session,
            from_user_id: None,
            system_name: None,
            task_id: None,
            from_run_id: None,
            from_agent_id: None,
        };

        // 3. Parse files into MediaItems
        let media = parse_files_arg(&args);

        // 4. Send via messenger
        match self
            .messenger
            .send_message(channel, chat_id, dialog_id, content, source, media)
            .await
        {
            Ok(OutboundDelivery::Delivered) => Ok(ToolResult {
                success: true,
                output: "消息已发送".to_string(),
                error: None,
            }),
            Ok(OutboundDelivery::AttachedToCurrentTurn) => Ok(ToolResult {
                success: true,
                output: "附件已加入当前回复".to_string(),
                error: None,
            }),
            Err(e) => Ok(ToolResult {
                success: false,
                output: String::new(),
                error: Some(e),
            }),
        }
    }
}

/// Parse the "files" argument into a Vec<MediaItem>, auto-detecting media type
/// from file extension using mime_guess.
fn parse_files_arg(args: &serde_json::Value) -> Vec<MediaItem> {
    let files = match args.get("files").and_then(|v| v.as_array()) {
        Some(arr) => arr,
        None => return Vec::new(),
    };

    files
        .iter()
        .filter_map(|v| v.as_str())
        .map(path_to_media_item)
        .collect()
}

/// Convert a file path to a MediaItem, detecting media_type and mime_type.
fn path_to_media_item(path: &str) -> MediaItem {
    let mime = mime_guess::from_path(path).first_or_octet_stream();
    let media_type = if mime.type_() == mime::IMAGE {
        "image"
    } else if mime.type_() == mime::AUDIO {
        "audio"
    } else if mime.type_() == mime::VIDEO {
        "video"
    } else {
        "file"
    };
    MediaItem {
        path: path.to_string(),
        media_type: media_type.to_string(),
        mime_type: Some(mime.to_string()),
        original_key: None,
    }
}

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

    #[test]
    fn test_parse_target_chat_id_two_part() {
        let (ch, cid, did) = parse_target_chat_id("feishu:oc_abc123").unwrap();
        assert_eq!(ch, "feishu");
        assert_eq!(cid, "oc_abc123");
        assert!(did.is_none());
    }

    #[test]
    fn test_parse_target_chat_id_three_part() {
        let (ch, cid, did) = parse_target_chat_id("feishu:oc_abc123:dialog1").unwrap();
        assert_eq!(ch, "feishu");
        assert_eq!(cid, "oc_abc123");
        assert_eq!(did, Some("dialog1"));
    }

    #[test]
    fn test_parse_target_chat_id_invalid_one_part() {
        assert!(parse_target_chat_id("feishu").is_err());
    }

    #[test]
    fn test_parse_target_chat_id_empty_parts() {
        assert!(parse_target_chat_id("feishu:").is_err());
        assert!(parse_target_chat_id(":chat_id").is_err());
        assert!(parse_target_chat_id("feishu::dialog").is_err());
    }
}
