#![cfg(target_os = "macos")]

use crate::worker::{ChatMessage, ConversationSummary, DaemonClient};
use anyhow::Result;
use std::collections::HashMap;
use std::ffi::{CStr, CString};

use xpc_connection::Message;

const SERVICE_NAME: &str = "net.buzzert.kordophonecd\0";

struct XpcTransport {
    connection: xpc_connection_sys::xpc_connection_t,
}

impl XpcTransport {
    fn connect(name: impl AsRef<CStr>) -> Self {
        use xpc_connection_sys::xpc_connection_create_mach_service;
        use xpc_connection_sys::xpc_connection_resume;

        let name = name.as_ref();
        let connection =
            unsafe { xpc_connection_create_mach_service(name.as_ptr(), std::ptr::null_mut(), 0) };

        unsafe { xpc_connection_resume(connection) };

        Self { connection }
    }

    fn send_with_reply(&self, message: Message) -> Message {
        use xpc_connection::message_to_xpc_object;
        use xpc_connection::xpc_object_to_message;
        use xpc_connection_sys::{xpc_connection_send_message_with_reply_sync, xpc_release};

        unsafe {
            let xobj = message_to_xpc_object(message);
            let reply = xpc_connection_send_message_with_reply_sync(self.connection, xobj);
            xpc_release(xobj);
            let msg = xpc_object_to_message(reply);
            if !reply.is_null() {
                xpc_release(reply);
            }
            msg
        }
    }
}

impl Drop for XpcTransport {
    fn drop(&mut self) {
        use xpc_connection_sys::xpc_object_t;
        use xpc_connection_sys::xpc_release;
        unsafe { xpc_release(self.connection as xpc_object_t) };
    }
}

pub(crate) struct XpcClient {
    transport: XpcTransport,
}

impl XpcClient {
    pub(crate) fn new() -> Result<Self> {
        let mach_port_name = CString::new(SERVICE_NAME).unwrap();
        Ok(Self {
            transport: XpcTransport::connect(&mach_port_name),
        })
    }

    fn key(s: &str) -> CString {
        CString::new(s).unwrap()
    }

    fn request(name: &str, arguments: Option<HashMap<CString, Message>>) -> Message {
        let mut root: HashMap<CString, Message> = HashMap::new();
        root.insert(Self::key("name"), Message::String(Self::key(name)));
        if let Some(args) = arguments {
            root.insert(Self::key("arguments"), Message::Dictionary(args));
        }
        Message::Dictionary(root)
    }

    fn get_string(map: &HashMap<CString, Message>, key: &str) -> Option<String> {
        match map.get(&Self::key(key)) {
            Some(Message::String(s)) => Some(s.to_string_lossy().into_owned()),
            _ => None,
        }
    }

    fn get_i64_from_str(map: &HashMap<CString, Message>, key: &str) -> i64 {
        Self::get_string(map, key)
            .and_then(|s| s.parse::<i64>().ok())
            .unwrap_or(0)
    }
}

impl DaemonClient for XpcClient {
    fn get_conversations(&mut self, limit: i32, offset: i32) -> Result<Vec<ConversationSummary>> {
        let mut args = HashMap::new();
        args.insert(Self::key("limit"), Message::String(Self::key(&limit.to_string())));
        args.insert(Self::key("offset"), Message::String(Self::key(&offset.to_string())));

        let reply = self
            .transport
            .send_with_reply(Self::request("GetConversations", Some(args)));

        let Message::Dictionary(map) = reply else {
            anyhow::bail!("Unexpected conversations response");
        };

        let Some(Message::Array(items)) = map.get(&Self::key("conversations")) else {
            anyhow::bail!("Missing conversations in response");
        };

        let mut conversations = Vec::new();
        for item in items {
            let Message::Dictionary(conv) = item else { continue };
            let id = Self::get_string(conv, "guid").unwrap_or_default();
            let display_name = Self::get_string(conv, "display_name").unwrap_or_default();
            let preview = Self::get_string(conv, "last_message_preview").unwrap_or_default();
            let unread_count = Self::get_i64_from_str(conv, "unread_count") as u32;
            let date_unix = Self::get_i64_from_str(conv, "date");

            let participants = match conv.get(&Self::key("participants")) {
                Some(Message::Array(arr)) => arr
                    .iter()
                    .filter_map(|m| match m {
                        Message::String(s) => Some(s.to_string_lossy().into_owned()),
                        _ => None,
                    })
                    .collect::<Vec<_>>(),
                _ => Vec::new(),
            };

            let title = if !display_name.trim().is_empty() {
                display_name
            } else if participants.is_empty() {
                "<unknown>".to_string()
            } else {
                participants.join(", ")
            };

            conversations.push(ConversationSummary {
                id,
                title,
                preview: preview.replace('\n', " "),
                participants,
                unread_count,
                date_unix,
            });
        }

        conversations.sort_by_key(|c| std::cmp::Reverse(c.date_unix));
        Ok(conversations)
    }

    fn get_messages(
        &mut self,
        conversation_id: String,
        last_message_id: Option<String>,
    ) -> Result<Vec<ChatMessage>> {
        let mut args = HashMap::new();
        args.insert(
            Self::key("conversation_id"),
            Message::String(Self::key(&conversation_id)),
        );
        if let Some(last) = last_message_id {
            args.insert(Self::key("last_message_id"), Message::String(Self::key(&last)));
        }

        let reply = self
            .transport
            .send_with_reply(Self::request("GetMessages", Some(args)));
        let Message::Dictionary(map) = reply else {
            anyhow::bail!("Unexpected messages response");
        };

        let Some(Message::Array(items)) = map.get(&Self::key("messages")) else {
            anyhow::bail!("Missing messages in response");
        };

        let mut messages = Vec::new();
        for item in items {
            let Message::Dictionary(msg) = item else { continue };
            messages.push(ChatMessage {
                id: Self::get_string(msg, "id").unwrap_or_default(),
                sender: Self::get_string(msg, "sender").unwrap_or_default(),
                text: Self::get_string(msg, "text").unwrap_or_default(),
                date_unix: Self::get_i64_from_str(msg, "date"),
                attachments: Vec::new(),
            });
        }
        Ok(messages)
    }

    fn send_message(&mut self, conversation_id: String, text: String) -> Result<Option<String>> {
        let mut args = HashMap::new();
        args.insert(
            Self::key("conversation_id"),
            Message::String(Self::key(&conversation_id)),
        );
        args.insert(Self::key("text"), Message::String(Self::key(&text)));

        let reply = self
            .transport
            .send_with_reply(Self::request("SendMessage", Some(args)));
        let Message::Dictionary(map) = reply else {
            anyhow::bail!("Unexpected send response");
        };

        Ok(Self::get_string(&map, "uuid"))
    }

    fn mark_conversation_as_read(&mut self, conversation_id: String) -> Result<()> {
        let mut args = HashMap::new();
        args.insert(
            Self::key("conversation_id"),
            Message::String(Self::key(&conversation_id)),
        );
        let _ = self
            .transport
            .send_with_reply(Self::request("MarkConversationAsRead", Some(args)));
        Ok(())
    }

    fn sync_conversation(&mut self, conversation_id: String) -> Result<()> {
        let mut args = HashMap::new();
        args.insert(
            Self::key("conversation_id"),
            Message::String(Self::key(&conversation_id)),
        );
        let _ = self
            .transport
            .send_with_reply(Self::request("SyncConversation", Some(args)));
        Ok(())
    }
}
