use super::agent::{XpcAgent, XpcConn, LOG_TARGET};
use kordophoned::daemon::events::Event;
use kordophoned::daemon::settings::Settings;
use std::collections::HashMap;
use std::ffi::CString;
use xpc_connection::Message;
use xpc_connection_sys as xpc_sys;

use super::util::*;
use super::DispatchResult;

pub async fn dispatch(
    agent: &XpcAgent,
    subscribers: &std::sync::Mutex<Vec<XpcConn>>,
    current_client: xpc_sys::xpc_connection_t,
    root: &HashMap<CString, Message>,
) -> DispatchResult {
    let request_id = dict_get_str(root, "request_id");

    let method = match dict_get_str(root, "method").or_else(|| dict_get_str(root, "type")) {
        Some(m) => m,
        None => {
            return DispatchResult::new(attach_request_id(
                make_error_reply("InvalidRequest", "Missing method/type"),
                request_id,
            ))
        }
    };

    let _arguments = get_dictionary_field(root, "arguments");

    let mut response = match method.as_str() {
        // GetVersion
        "GetVersion" => match agent.send_event(Event::GetVersion).await {
            Ok(version) => {
                let mut reply: XpcMap = HashMap::new();
                dict_put_str(&mut reply, "type", "GetVersionResponse");
                dict_put_str(&mut reply, "version", &version);
                DispatchResult::new(Message::Dictionary(reply))
            }
            Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
        },

        // GetConversations
        "GetConversations" => {
            let mut limit: i32 = 100;
            let mut offset: i32 = 0;
            if let Some(args) = get_dictionary_field(root, "arguments") {
                if let Some(v) = dict_get_i64_from_str(args, "limit") {
                    limit = v as i32;
                }
                if let Some(v) = dict_get_i64_from_str(args, "offset") {
                    offset = v as i32;
                }
            }
            match agent
                .send_event(|r| Event::GetAllConversations(limit, offset, r))
                .await
            {
                Ok(conversations) => {
                    let mut items: Vec<Message> = Vec::with_capacity(conversations.len());
                    for conv in conversations {
                        let mut m: XpcMap = HashMap::new();
                        dict_put_str(&mut m, "guid", &conv.guid);
                        dict_put_str(
                            &mut m,
                            "display_name",
                            &conv.display_name.unwrap_or_default(),
                        );
                        dict_put_i64_as_str(&mut m, "unread_count", conv.unread_count as i64);
                        dict_put_str(
                            &mut m,
                            "last_message_preview",
                            &conv.last_message_preview.unwrap_or_default(),
                        );
                        let participant_names: Vec<String> = conv
                            .participants
                            .into_iter()
                            .map(|p| p.display_name())
                            .collect();
                        m.insert(cstr("participants"), array_from_strs(participant_names));
                        dict_put_i64_as_str(&mut m, "date", conv.date.and_utc().timestamp());
                        items.push(Message::Dictionary(m));
                    }
                    let mut reply: XpcMap = HashMap::new();
                    dict_put_str(&mut reply, "type", "GetConversationsResponse");
                    reply.insert(cstr("conversations"), Message::Array(items));
                    DispatchResult::new(Message::Dictionary(reply))
                }
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // Sync ops
        "SyncConversationList" => match agent.send_event(Event::SyncConversationList).await {
            Ok(()) => DispatchResult::new(make_ok_reply()),
            Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
        },
        "SyncAllConversations" => match agent.send_event(Event::SyncAllConversations).await {
            Ok(()) => DispatchResult::new(make_ok_reply()),
            Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
        },
        "SyncConversation" => {
            let conversation_id = match get_dictionary_field(root, "arguments")
                .and_then(|m| dict_get_str(m, "conversation_id"))
            {
                Some(id) => id,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
            };
            match agent
                .send_event(|r| Event::SyncConversation(conversation_id, r))
                .await
            {
                Ok(()) => DispatchResult::new(make_ok_reply()),
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // Mark as read
        "MarkConversationAsRead" => {
            let conversation_id = match get_dictionary_field(root, "arguments")
                .and_then(|m| dict_get_str(m, "conversation_id"))
            {
                Some(id) => id,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
            };
            match agent
                .send_event(|r| Event::MarkConversationAsRead(conversation_id, r))
                .await
            {
                Ok(()) => DispatchResult::new(make_ok_reply()),
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // GetMessages
        "GetMessages" => {
            let args = match get_dictionary_field(root, "arguments") {
                Some(a) => a,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
            };
            let conversation_id = match dict_get_str(args, "conversation_id") {
                Some(id) => id,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
            };
            let last_message_id = dict_get_str(args, "last_message_id");
            match agent
                .send_event(|r| Event::GetMessages(conversation_id, last_message_id, r))
                .await
            {
                Ok(messages) => {
                    let mut items: Vec<Message> = Vec::with_capacity(messages.len());
                    for msg in messages {
                        let mut m: XpcMap = HashMap::new();
                        dict_put_str(&mut m, "id", &msg.id);
                        dict_put_str(&mut m, "text", &msg.text.replace('\u{FFFC}', ""));
                        dict_put_i64_as_str(&mut m, "date", msg.date.and_utc().timestamp());
                        dict_put_str(&mut m, "sender", &msg.sender.display_name());

                        // Include attachment GUIDs for the client to resolve/download
                        let attachment_guids: Vec<String> = msg
                            .attachments
                            .iter()
                            .map(|a| a.guid.clone())
                            .collect();
                        m.insert(cstr("attachment_guids"), array_from_strs(attachment_guids));
                        
                        // Full attachments array with metadata (mirrors DBus fields)
                        let mut attachments_items: Vec<Message> = Vec::new();
                        for attachment in msg.attachments.iter() {
                            let mut a: XpcMap = HashMap::new();
                            // Basic identifiers
                            dict_put_str(&mut a, "guid", &attachment.guid);

                            // Paths and download status
                            let path = attachment.get_path_for_preview(false);
                            let preview_path = attachment.get_path_for_preview(true);
                            let downloaded = attachment.is_downloaded(false);
                            let preview_downloaded = attachment.is_downloaded(true);

                            dict_put_str(&mut a, "path", &path.to_string_lossy());
                            dict_put_str(&mut a, "preview_path", &preview_path.to_string_lossy());
                            dict_put_str(&mut a, "downloaded", &downloaded.to_string());
                            dict_put_str(
                                &mut a,
                                "preview_downloaded",
                                &preview_downloaded.to_string(),
                            );

                            // Metadata (optional)
                            if let Some(metadata) = &attachment.metadata {
                                let mut metadata_map: XpcMap = HashMap::new();
                                if let Some(attribution_info) = &metadata.attribution_info {
                                    let mut attribution_map: XpcMap = HashMap::new();
                                    if let Some(width) = attribution_info.width {
                                        dict_put_i64_as_str(&mut attribution_map, "width", width as i64);
                                    }
                                    if let Some(height) = attribution_info.height {
                                        dict_put_i64_as_str(&mut attribution_map, "height", height as i64);
                                    }
                                    metadata_map.insert(cstr("attribution_info"), Message::Dictionary(attribution_map));
                                }
                                if !metadata_map.is_empty() {
                                    a.insert(cstr("metadata"), Message::Dictionary(metadata_map));
                                }
                            }

                            attachments_items.push(Message::Dictionary(a));
                        }
                        m.insert(cstr("attachments"), Message::Array(attachments_items));
                        
                        items.push(Message::Dictionary(m));
                    }
                    let mut reply: XpcMap = HashMap::new();
                    dict_put_str(&mut reply, "type", "GetMessagesResponse");
                    reply.insert(cstr("messages"), Message::Array(items));
                    DispatchResult::new(Message::Dictionary(reply))
                }
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // Delete all
        "DeleteAllConversations" => match agent.send_event(Event::DeleteAllConversations).await {
            Ok(()) => DispatchResult::new(make_ok_reply()),
            Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
        },

        // SendMessage
        "SendMessage" => {
            let args = match get_dictionary_field(root, "arguments") {
                Some(a) => a,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
            };
            let conversation_id = match dict_get_str(args, "conversation_id") {
                Some(v) => v,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing conversation_id")),
            };
            let text = dict_get_str(args, "text").unwrap_or_default();
            let attachment_guids: Vec<String> = match args.get(&cstr("attachment_guids")) {
                Some(Message::Array(arr)) => arr
                    .iter()
                    .filter_map(|m| match m {
                        Message::String(s) => Some(s.to_string_lossy().into_owned()),
                        _ => None,
                    })
                    .collect(),
                _ => Vec::new(),
            };
            match agent
                .send_event(|r| Event::SendMessage(conversation_id, text, attachment_guids, r))
                .await
            {
                Ok(uuid) => {
                    let mut reply: XpcMap = HashMap::new();
                    dict_put_str(&mut reply, "type", "SendMessageResponse");
                    dict_put_str(&mut reply, "uuid", &uuid.to_string());
                    DispatchResult::new(Message::Dictionary(reply))
                }
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // GetAttachmentInfo
        "GetAttachmentInfo" => {
            let args = match get_dictionary_field(root, "arguments") {
                Some(a) => a,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
            };
            let attachment_id = match dict_get_str(args, "attachment_id") {
                Some(v) => v,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")),
            };
            match agent
                .send_event(|r| Event::GetAttachment(attachment_id, r))
                .await
            {
                Ok(attachment) => {
                    let mut reply: XpcMap = HashMap::new();
                    dict_put_str(&mut reply, "type", "GetAttachmentInfoResponse");
                    dict_put_str(
                        &mut reply,
                        "path",
                        &attachment.get_path_for_preview(false).to_string_lossy(),
                    );
                    dict_put_str(
                        &mut reply,
                        "preview_path",
                        &attachment.get_path_for_preview(true).to_string_lossy(),
                    );
                    dict_put_str(
                        &mut reply,
                        "downloaded",
                        &attachment.is_downloaded(false).to_string(),
                    );
                    dict_put_str(
                        &mut reply,
                        "preview_downloaded",
                        &attachment.is_downloaded(true).to_string(),
                    );
                    DispatchResult::new(Message::Dictionary(reply))
                }
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // OpenAttachmentFd (return file descriptor in reply)
        "OpenAttachmentFd" => {
            let args = match get_dictionary_field(root, "arguments") {
                Some(a) => a,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
            };
            let attachment_id = match dict_get_str(args, "attachment_id") {
                Some(v) => v,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")),
            };
            let preview = dict_get_str(args, "preview")
                .map(|s| s == "true")
                .unwrap_or(false);

            match agent
                .send_event(|r| Event::GetAttachment(attachment_id, r))
                .await
            {
                Ok(attachment) => {
                    use std::os::fd::AsRawFd;
                    
                    let path = attachment.get_path_for_preview(preview);
                    match std::fs::OpenOptions::new().read(true).open(&path) {
                        Ok(file) => {
                            use std::os::fd::AsRawFd;
                            let fd = file.as_raw_fd();

                            let mut reply: XpcMap = HashMap::new();
                            dict_put_str(&mut reply, "type", "OpenAttachmentFdResponse");
                            reply.insert(cstr("fd"), Message::Fd(fd));

                            DispatchResult { message: Message::Dictionary(reply), cleanup: Some(Box::new(file)) }
                        }
                        Err(e) => DispatchResult::new(make_error_reply("OpenFailed", &format!("{}", e))),
                    }
                }
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // DownloadAttachment
        "DownloadAttachment" => {
            let args = match get_dictionary_field(root, "arguments") {
                Some(a) => a,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
            };
            let attachment_id = match dict_get_str(args, "attachment_id") {
                Some(v) => v,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing attachment_id")),
            };
            let preview = dict_get_str(args, "preview")
                .map(|s| s == "true")
                .unwrap_or(false);
            match agent
                .send_event(|r| Event::DownloadAttachment(attachment_id, preview, r))
                .await
            {
                Ok(()) => DispatchResult::new(make_ok_reply()),
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // UploadAttachment
        "UploadAttachment" => {
            use std::path::PathBuf;
            let args = match get_dictionary_field(root, "arguments") {
                Some(a) => a,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
            };
            let path = match dict_get_str(args, "path") {
                Some(v) => v,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing path")),
            };
            match agent
                .send_event(|r| Event::UploadAttachment(PathBuf::from(path), r))
                .await
            {
                Ok(upload_guid) => {
                    let mut reply: XpcMap = HashMap::new();
                    dict_put_str(&mut reply, "type", "UploadAttachmentResponse");
                    dict_put_str(&mut reply, "upload_guid", &upload_guid);
                    DispatchResult::new(Message::Dictionary(reply))
                }
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // Settings
        "GetAllSettings" => match agent.send_event(Event::GetAllSettings).await {
            Ok(settings) => {
                let mut reply: XpcMap = HashMap::new();
                dict_put_str(&mut reply, "type", "GetAllSettingsResponse");
                dict_put_str(
                    &mut reply,
                    "server_url",
                    &settings.server_url.unwrap_or_default(),
                );
                dict_put_str(
                    &mut reply,
                    "username",
                    &settings.username.unwrap_or_default(),
                );
                DispatchResult::new(Message::Dictionary(reply))
            }
            Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
        },
        "UpdateSettings" => {
            let args = match get_dictionary_field(root, "arguments") {
                Some(a) => a,
                None => return DispatchResult::new(make_error_reply("InvalidRequest", "Missing arguments")),
            };
            let server_url = dict_get_str(args, "server_url");
            let username = dict_get_str(args, "username");
            let settings = Settings {
                server_url,
                username,
                token: None,
            };
            match agent
                .send_event(|r| Event::UpdateSettings(settings, r))
                .await
            {
                Ok(()) => DispatchResult::new(make_ok_reply()),
                Err(e) => DispatchResult::new(make_error_reply("DaemonError", &format!("{}", e))),
            }
        }

        // Subscribe
        "SubscribeSignals" => {
            let mut list = subscribers.lock().unwrap();
            if !list.iter().any(|c| c.0 == current_client) {
                list.push(XpcConn(current_client));
                log::trace!(target: LOG_TARGET, "Client subscribed to signals (total subscribers: {})", list.len());
            }
            DispatchResult::new(make_ok_reply())
        }

        // Unknown method fallback
        other => DispatchResult::new(make_error_reply("UnknownMethod", other)),
    };

    response.message = attach_request_id(response.message, request_id);
    response
}
