use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

use crate::domain::messages::ToolCall;

pub const SYSTEM_CONTEXT_AGENT_PROMPT: &str = "agent_prompt";
pub const SYSTEM_CONTEXT_SCHEDULED_PROMPT: &str = "scheduled_system_prompt";
pub const SYSTEM_CONTEXT_HISTORY_COMPACTION: &str = "history_compaction";

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ToolMessageState {
    Completed,
    PendingUserAction,
}

// ============================================================================
// MediaItem - Media metadata for messages
// ============================================================================

#[derive(Debug, Clone)]
pub struct MediaItem {
    pub path: String,       // Local file path
    pub media_type: String, // "image", "audio", "file", "video"
    pub mime_type: Option<String>,
    pub original_key: Option<String>, // Feishu file_key for download
    pub content_base64: Option<String>, // Base64-encoded file content for web download
    pub file_name: Option<String>,      // Display file name
}

impl MediaItem {
    pub fn new(path: impl Into<String>, media_type: impl Into<String>) -> Self {
        Self {
            path: path.into(),
            media_type: media_type.into(),
            mime_type: None,
            original_key: None,
            content_base64: None,
            file_name: None,
        }
    }
}

// ============================================================================
// ChatMessage - Used by AgentLoop for LLM conversation history
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
    pub id: String,
    pub role: String,
    pub content: String,
    pub media_refs: Vec<String>, // Paths to media files for context
    pub timestamp: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub system_context: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reasoning_content: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_state: Option<ToolMessageState>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_duration_ms: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCall>>,
}

impl ChatMessage {
    pub fn user(content: impl Into<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "user".to_string(),
            content: content.into(),
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            system_context: None,
            reasoning_content: None,
            tool_call_id: None,
            tool_name: None,
            tool_duration_ms: None,
            tool_state: None,
            tool_calls: None,
        }
    }

    pub fn user_with_media(content: impl Into<String>, media_refs: Vec<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "user".to_string(),
            content: content.into(),
            media_refs,
            timestamp: current_timestamp(),
            system_context: None,
            reasoning_content: None,
            tool_call_id: None,
            tool_name: None,
            tool_duration_ms: None,
            tool_state: None,
            tool_calls: None,
        }
    }

    pub fn assistant(content: impl Into<String>) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "assistant".to_string(),
            content: content.into(),
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            system_context: None,
            reasoning_content: None,
            tool_call_id: None,
            tool_name: None,
            tool_duration_ms: None,
            tool_state: None,
            tool_calls: None,
        }
    }

    pub fn assistant_with_reasoning(
        content: impl Into<String>,
        reasoning_content: impl Into<String>,
    ) -> Self {
        let mut message = Self::assistant(content);
        message.reasoning_content = Some(reasoning_content.into());
        message
    }

    pub fn assistant_with_tool_calls(
        content: impl Into<String>,
        tool_calls: Vec<ToolCall>,
    ) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "assistant".to_string(),
            content: content.into(),
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            system_context: None,
            reasoning_content: None,
            tool_call_id: None,
            tool_name: None,
            tool_duration_ms: None,
            tool_state: None,
            tool_calls: Some(tool_calls),
        }
    }

    pub fn assistant_with_tool_calls_and_reasoning(
        content: impl Into<String>,
        tool_calls: Vec<ToolCall>,
        reasoning_content: impl Into<String>,
    ) -> Self {
        let mut message = Self::assistant_with_tool_calls(content, tool_calls);
        message.reasoning_content = Some(reasoning_content.into());
        message
    }

    pub fn system(content: impl Into<String>) -> Self {
        Self::system_with_context(content, None::<String>)
    }

    pub fn system_with_context(
        content: impl Into<String>,
        system_context: impl Into<Option<String>>,
    ) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "system".to_string(),
            content: content.into(),
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            system_context: system_context.into(),
            reasoning_content: None,
            tool_call_id: None,
            tool_name: None,
            tool_duration_ms: None,
            tool_state: None,
            tool_calls: None,
        }
    }

    pub fn tool(
        tool_call_id: impl Into<String>,
        tool_name: impl Into<String>,
        content: impl Into<String>,
    ) -> Self {
        Self::tool_with_state(
            tool_call_id,
            tool_name,
            content,
            ToolMessageState::Completed,
        )
    }

    pub fn tool_with_state(
        tool_call_id: impl Into<String>,
        tool_name: impl Into<String>,
        content: impl Into<String>,
        tool_state: ToolMessageState,
    ) -> Self {
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            role: "tool".to_string(),
            content: content.into(),
            media_refs: Vec::new(),
            timestamp: current_timestamp(),
            system_context: None,
            reasoning_content: None,
            tool_call_id: Some(tool_call_id.into()),
            tool_name: Some(tool_name.into()),
            tool_duration_ms: None,
            tool_state: Some(tool_state),
            tool_calls: None,
        }
    }

    pub fn with_tool_duration(mut self, ms: u64) -> Self {
        self.tool_duration_ms = Some(ms);
        self
    }

    pub fn has_system_context(&self, expected: &str) -> bool {
        self.system_context.as_deref() == Some(expected)
    }

    pub fn is_assistant_tool_call_message(&self) -> bool {
        self.role == "assistant"
            && self
                .tool_calls
                .as_ref()
                .map(|calls| !calls.is_empty())
                .unwrap_or(false)
    }
}

// ============================================================================
// Message sanitization
// ============================================================================

/// Sanitize message history by removing assistant messages with `tool_calls`
/// that don't have corresponding tool result messages, at ANY position in
/// the history (not just trailing).
///
/// Incomplete sequences can appear in the middle of history when:
/// 1. The process was interrupted mid-execution, then a new user message
///    was appended, burying the orphan.
/// 2. History compaction preserves orphaned `tool_calls` from a pre-fix era
///    or from a race condition between persistence and snapshot.
///
/// Sending such incomplete sequences to the API causes errors like
/// "insufficient tool messages following tool_calls message".
///
/// Returns the number of messages removed.
pub(crate) fn sanitize_incomplete_tool_call_sequences(messages: &mut Vec<ChatMessage>) -> usize {
    let mut removed = 0;

    // Phase 1: Single reverse pass to find ALL assistant messages with
    // incomplete tool_calls, regardless of position.
    //
    // Scanning right-to-left means we encounter tool results before their
    // parent assistants, so we naturally know which tool_call_ids have
    // corresponding results.
    let mut resolved_ids: HashSet<String> = HashSet::new();
    let mut with_parent: HashSet<String> = HashSet::new();
    let mut remove_indices: Vec<usize> = Vec::new();

    // Reverse pass — collect tool result IDs first, then validate each
    // assistant's tool_calls against already-seen results.
    //
    // Because we scan right-to-left, any tool result we've already seen
    // appears AFTER the current message in forward order. This correctly
    // identifies which assistant tool_calls have corresponding results.
    for i in (0..messages.len()).rev() {
        let msg = &messages[i];

        if msg.role == "tool" {
            if let Some(ref tc_id) = msg.tool_call_id {
                resolved_ids.insert(tc_id.clone());
            }
        }

        if msg.role == "assistant"
            && msg.tool_calls.as_ref().map_or(false, |calls| !calls.is_empty())
        {
            let tool_calls = msg.tool_calls.as_ref().unwrap();
            let all_have_results = tool_calls
                .iter()
                .all(|tc| resolved_ids.contains(&tc.id));

            if all_have_results {
                for tc in tool_calls.iter() {
                    with_parent.insert(tc.id.clone());
                }
            } else {
                let missing_count = tool_calls
                    .iter()
                    .filter(|tc| !resolved_ids.contains(&tc.id))
                    .count();

                tracing::warn!(
                    tool_call_count = tool_calls.len(),
                    missing_tool_results = missing_count,
                    message_id = %msg.id,
                    message_index = i,
                    "Removing assistant message with incomplete tool call sequence — \
                     tool results were never persisted (likely due to process interruption \
                     or history compaction preserving an orphan)"
                );

                remove_indices.push(i);
            }
        }
    }

    // Remove in descending index order to avoid shifting
    for &idx in &remove_indices {
        messages.remove(idx);
        removed += 1;
    }

    // Phase 2: Forward pass to remove ALL orphaned tool messages (not just
    // trailing ones). A tool message is orphaned if its tool_call_id has no
    // matching parent assistant remaining in the history.
    if !with_parent.is_empty() || !resolved_ids.is_empty() {
        let mut i = 0;
        while i < messages.len() {
            let msg = &messages[i];
            if msg.role == "tool" {
                let is_orphaned = match &msg.tool_call_id {
                    Some(tc_id) => !with_parent.contains(tc_id),
                    None => true,
                };
                if is_orphaned {
                    tracing::warn!(
                        tool_call_id = ?msg.tool_call_id,
                        message_id = %msg.id,
                        message_index = i,
                        "Removing orphaned tool result message — its parent assistant \
                         tool_calls message was removed or never persisted"
                    );
                    messages.remove(i);
                    removed += 1;
                    continue;
                }
            }
            i += 1;
        }
    }

    removed
}

// ============================================================================
// InboundMessage - Message from Channel to Bus (user input)
// ============================================================================

#[derive(Debug, Clone)]
pub struct InboundMessage {
    pub channel: String,
    pub sender_id: String,
    pub chat_id: String,
    pub content: String,
    pub timestamp: i64,
    pub media: Vec<MediaItem>,
    /// Channel-specific data used internally by the channel (not forwarded).
    pub metadata: HashMap<String, String>,
    /// Data forwarded from inbound to outbound (copied to OutboundMessage.metadata by gateway).
    pub forwarded_metadata: HashMap<String, String>,
}

impl InboundMessage {
    pub fn session_key(&self) -> String {
        format!("{}:{}", self.channel, self.chat_id)
    }
}

// ============================================================================
// OutboundMessage - Message from Agent to Channel (bot response)
// ============================================================================

#[derive(Debug, Clone)]
pub struct OutboundMessage {
    pub channel: String,
    /// 消息发送目标 ID（如飞书 open_id、微信 chat_id）
    /// 注意：这始终是原始入站消息的 chat_id，不会被修改为会话 ID
    pub chat_id: String,
    /// 内部会话 ID（对应 sessions.id）
    /// 用于会话管理和消息持久化，与消息发送目标无关
    pub session_id: Option<String>,
    pub content: String,
    pub reply_to: Option<String>,
    pub media: Vec<MediaItem>,
    pub metadata: HashMap<String, String>,
    pub event_kind: OutboundEventKind,
    pub role: String,
    pub tool_call_id: Option<String>,
    pub tool_name: Option<String>,
    pub tool_arguments: Option<serde_json::Value>,
    pub reasoning_content: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OutboundEventKind {
    AssistantResponse,
    ToolCall,
    ToolResult,
    ToolPending,
    SchedulerNotification,
    ErrorNotification,
    TaskStarted,
}

impl OutboundMessage {
    pub fn is_stream_delta(&self) -> bool {
        self.metadata.get("_stream_delta").is_some()
    }

    pub fn assistant(
        channel: impl Into<String>,
        chat_id: impl Into<String>,
        session_id: Option<String>,
        content: impl Into<String>,
        reply_to: Option<String>,
        metadata: HashMap<String, String>,
    ) -> Self {
        Self {
            channel: channel.into(),
            chat_id: chat_id.into(),
            session_id,
            content: content.into(),
            reply_to,
            media: Vec::new(),
            metadata,
            event_kind: OutboundEventKind::AssistantResponse,
            role: "assistant".to_string(),
            tool_call_id: None,
            tool_name: None,
            tool_arguments: None,
            reasoning_content: None,
        }
    }

    pub fn scheduler_notification(
        channel: impl Into<String>,
        chat_id: impl Into<String>,
        session_id: Option<String>,
        content: impl Into<String>,
        reply_to: Option<String>,
        metadata: HashMap<String, String>,
    ) -> Self {
        let mut message = Self::assistant(channel, chat_id, session_id, content, reply_to, metadata);
        message.event_kind = OutboundEventKind::SchedulerNotification;
        message
    }

    pub fn error_notification(
        channel: impl Into<String>,
        chat_id: impl Into<String>,
        session_id: Option<String>,
        content: impl Into<String>,
        reply_to: Option<String>,
        metadata: HashMap<String, String>,
    ) -> Self {
        let mut message = Self::assistant(channel, chat_id, session_id, content, reply_to, metadata);
        message.event_kind = OutboundEventKind::ErrorNotification;
        message
    }

    pub fn tool_call(
        channel: impl Into<String>,
        chat_id: impl Into<String>,
        session_id: Option<String>,
        message_id: impl Into<String>,
        tool_name: impl Into<String>,
        tool_arguments: serde_json::Value,
        reply_to: Option<String>,
        metadata: HashMap<String, String>,
    ) -> Self {
        let tool_name = tool_name.into();
        let content = format_tool_call_content(&tool_name, &tool_arguments);
        Self {
            channel: channel.into(),
            chat_id: chat_id.into(),
            session_id,
            content,
            reply_to,
            media: Vec::new(),
            metadata,
            event_kind: OutboundEventKind::ToolCall,
            role: "assistant".to_string(),
            tool_call_id: Some(message_id.into()),
            tool_name: Some(tool_name),
            tool_arguments: Some(tool_arguments),
            reasoning_content: None,
        }
    }

    pub fn tool_result(
        channel: impl Into<String>,
        chat_id: impl Into<String>,
        session_id: Option<String>,
        tool_call_id: impl Into<String>,
        tool_name: impl Into<String>,
        content: impl Into<String>,
        reply_to: Option<String>,
        metadata: HashMap<String, String>,
    ) -> Self {
        let tool_name = tool_name.into();
        let raw_content = content.into();
        let content = format_tool_result_content(&tool_name, &raw_content);
        Self {
            channel: channel.into(),
            chat_id: chat_id.into(),
            session_id,
            content,
            reply_to,
            media: Vec::new(),
            metadata,
            event_kind: OutboundEventKind::ToolResult,
            role: "tool".to_string(),
            tool_call_id: Some(tool_call_id.into()),
            tool_name: Some(tool_name),
            tool_arguments: None,
            reasoning_content: None,
        }
    }

    pub fn tool_pending(
        channel: impl Into<String>,
        chat_id: impl Into<String>,
        session_id: Option<String>,
        tool_call_id: impl Into<String>,
        tool_name: impl Into<String>,
        content: impl Into<String>,
        reply_to: Option<String>,
        metadata: HashMap<String, String>,
    ) -> Self {
        let tool_name = tool_name.into();
        let raw_content = content.into();
        let content = format_tool_result_content(&tool_name, &raw_content);
        Self {
            channel: channel.into(),
            chat_id: chat_id.into(),
            session_id,
            content,
            reply_to,
            media: Vec::new(),
            metadata,
            event_kind: OutboundEventKind::ToolPending,
            role: "tool".to_string(),
            tool_call_id: Some(tool_call_id.into()),
            tool_name: Some(tool_name),
            tool_arguments: None,
            reasoning_content: None,
        }
    }

    pub fn from_chat_message(
        channel: &str,
        chat_id: &str,
        session_id: Option<String>,
        reply_to: Option<String>,
        metadata: &HashMap<String, String>,
        message: &ChatMessage,
    ) -> Vec<Self> {
        match message.role.as_str() {
            "assistant" => {
                if let Some(tool_calls) = &message.tool_calls {
                    let mut outbound = Vec::new();
                    let has_content_or_reasoning = !message.content.trim().is_empty() || message.reasoning_content.is_some();
                    if has_content_or_reasoning {
                        let mut resp = Self::assistant(
                            channel.to_string(),
                            chat_id.to_string(),
                            session_id.clone(),
                            message.content.clone(),
                            reply_to.clone(),
                            metadata.clone(),
                        );
                        resp.reasoning_content = message.reasoning_content.clone();
                        outbound.push(resp);
                    }

                    // AssistantResponse 已携带 reasoning 时，ToolCall 不再重复；
                    // 只有 AssistantResponse 没发时，ToolCall 才带 reasoning
                    let tc_reasoning = if has_content_or_reasoning { None } else { message.reasoning_content.clone() };
                    outbound.extend(tool_calls.iter().map(|tool_call| {
                        let mut tc = Self::tool_call(
                            channel.to_string(),
                            chat_id.to_string(),
                            session_id.clone(),
                            tool_call.id.clone(),
                            tool_call.name.clone(),
                            tool_call.arguments.clone(),
                            reply_to.clone(),
                            metadata.clone(),
                        );
                        tc.reasoning_content = tc_reasoning.clone();
                        tc
                    }));
                    outbound
                } else {
                    let mut resp = Self::assistant(
                        channel.to_string(),
                        chat_id.to_string(),
                        session_id,
                        message.content.clone(),
                        reply_to,
                        metadata.clone(),
                    );
                    resp.reasoning_content = message.reasoning_content.clone();
                    vec![resp]
                }
            }
            "tool" => match message
                .tool_state
                .as_ref()
                .unwrap_or(&ToolMessageState::Completed)
            {
                ToolMessageState::Completed => vec![Self::tool_result(
                    channel.to_string(),
                    chat_id.to_string(),
                    session_id,
                    message.tool_call_id.clone().unwrap_or_default(),
                    message.tool_name.clone().unwrap_or_default(),
                    message.content.clone(),
                    reply_to,
                    metadata.clone(),
                )],
                ToolMessageState::PendingUserAction => vec![Self::tool_pending(
                    channel.to_string(),
                    chat_id.to_string(),
                    session_id,
                    message.tool_call_id.clone().unwrap_or_default(),
                    message.tool_name.clone().unwrap_or_default(),
                    message.content.clone(),
                    reply_to,
                    metadata.clone(),
                )],
            },
            _ => Vec::new(),
        }
    }
}

pub(crate) fn format_tool_call_content(
    tool_name: &str,
    tool_arguments: &serde_json::Value,
) -> String {
    match tool_arguments {
        serde_json::Value::Object(map) if map.is_empty() => tool_name.to_string(),
        other => format!("{}\nargs: {}", tool_name, format_tool_arguments_json(other)),
    }
}

fn format_tool_result_content(tool_name: &str, content: &str) -> String {
    format!("工具结果: {}\n\n{}", tool_name, content)
}

fn format_tool_argument_value(value: &serde_json::Value) -> String {
    match value {
        serde_json::Value::String(text) => text.clone(),
        serde_json::Value::Null => "null".to_string(),
        other => serde_json::to_string(other).unwrap_or_else(|_| other.to_string()),
    }
}

fn format_tool_arguments_json(value: &serde_json::Value) -> String {
    match value {
        serde_json::Value::Object(map) => {
            let mut entries: Vec<_> = map.iter().collect();
            entries.sort_by(|(left, _), (right, _)| left.cmp(right));
            let body = entries
                .into_iter()
                .map(|(key, value)| {
                    format!(
                        "{}:{}",
                        serde_json::to_string(key).unwrap_or_else(|_| format!("\"{}\"", key)),
                        serde_json::to_string(value).unwrap_or_else(|_| value.to_string()),
                    )
                })
                .collect::<Vec<_>>()
                .join(",");
            format!("{{{}}}", body)
        }
        other => format_tool_argument_value(other),
    }
}

// ============================================================================
// Helpers
// ============================================================================

fn current_timestamp() -> i64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_millis() as i64
}

#[cfg(test)]
mod tests {
    use super::{ChatMessage, OutboundEventKind, OutboundMessage, ToolMessageState};
    use crate::domain::messages::ToolCall;
    use serde_json::json;
    use std::collections::HashMap;

    const TEST_CHANNEL: &str = "test-channel";

    #[test]
    fn test_from_chat_message_expands_tool_calls() {
        let message = ChatMessage::assistant_with_tool_calls(
            "",
            vec![
                ToolCall {
                    id: "call-1".to_string(),
                    name: "calculator".to_string(),
                    arguments: json!({"expression": "1 + 1"}),
                },
                ToolCall {
                    id: "call-2".to_string(),
                    name: "read".to_string(),
                    arguments: json!({"path": "README.md"}),
                },
            ],
        );

        let outbound = OutboundMessage::from_chat_message(
            TEST_CHANNEL,
            "chat-1",
            None,
            None,
            &HashMap::new(),
            &message,
        );

        assert_eq!(outbound.len(), 2);
        assert_eq!(outbound[0].event_kind, OutboundEventKind::ToolCall);
        assert_eq!(outbound[0].tool_name.as_deref(), Some("calculator"));
        assert_eq!(
            outbound[0].tool_arguments.as_ref().unwrap()["expression"],
            "1 + 1"
        );
        assert_eq!(
            outbound[0].content,
            "calculator\nargs: {\"expression\":\"1 + 1\"}"
        );
        assert_eq!(outbound[1].tool_name.as_deref(), Some("read"));
        assert_eq!(
            outbound[1].content,
            "read\nargs: {\"path\":\"README.md\"}"
        );
    }

    #[test]
    fn test_from_chat_message_keeps_assistant_content_when_tool_calls_exist() {
        let message = ChatMessage::assistant_with_tool_calls(
            "日报已整理完成。",
            vec![ToolCall {
                id: "call-1".to_string(),
                name: "memory_manage".to_string(),
                arguments: json!({"action": "put"}),
            }],
        );

        let outbound = OutboundMessage::from_chat_message(
            TEST_CHANNEL,
            "chat-1",
            None,
            None,
            &HashMap::new(),
            &message,
        );

        assert_eq!(outbound.len(), 2);
        assert_eq!(outbound[0].event_kind, OutboundEventKind::AssistantResponse);
        assert_eq!(outbound[0].content, "日报已整理完成。");
        assert_eq!(outbound[1].event_kind, OutboundEventKind::ToolCall);
        assert_eq!(outbound[1].tool_name.as_deref(), Some("memory_manage"));
    }

    #[test]
    fn test_from_chat_message_includes_tool_result() {
        let message = ChatMessage::tool("call-9", "calculator", "2");

        let outbound = OutboundMessage::from_chat_message(
            TEST_CHANNEL,
            "chat-1",
            None,
            None,
            &HashMap::new(),
            &message,
        );

        assert_eq!(outbound.len(), 1);
        assert_eq!(outbound[0].event_kind, OutboundEventKind::ToolResult);
    }

    #[test]
    fn test_from_chat_message_includes_tool_pending() {
        let message = ChatMessage::tool_with_state(
            "call-9",
            "bash",
            "等待你完成浏览器授权后再继续。",
            ToolMessageState::PendingUserAction,
        );

        let outbound = OutboundMessage::from_chat_message(
            TEST_CHANNEL,
            "chat-1",
            None,
            None,
            &HashMap::new(),
            &message,
        );

        assert_eq!(outbound.len(), 1);
        assert_eq!(outbound[0].event_kind, OutboundEventKind::ToolPending);
    }
}
