use crate::xpc::interface::SERVICE_NAME;
use kordophoned::daemon::{events::Event, signals::Signal, DaemonResult};
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot, Mutex};
use xpc_connection::{message_to_xpc_object, xpc_object_to_message, Message, MessageError};
use xpc_connection_sys as xpc_sys;

pub(super) static LOG_TARGET: &str = "xpc";

/// Wrapper for raw XPC connection pointer to declare cross-thread usage.
/// Safety: libxpc connections are reference-counted and may be used to send from other threads.
#[derive(Copy, Clone)]
pub(crate) struct XpcConn(pub xpc_sys::xpc_connection_t);
unsafe impl Send for XpcConn {}
unsafe impl Sync for XpcConn {}

type Subscribers = Arc<std::sync::Mutex<Vec<XpcConn>>>;

#[derive(Clone)]
pub struct XpcAgent {
    event_sink: mpsc::Sender<Event>,
    signal_receiver: Arc<Mutex<Option<mpsc::Receiver<Signal>>>>,
}

impl XpcAgent {
    pub fn new(event_sink: mpsc::Sender<Event>, signal_receiver: mpsc::Receiver<Signal>) -> Self {
        Self {
            event_sink,
            signal_receiver: Arc::new(Mutex::new(Some(signal_receiver))),
        }
    }

    pub async fn run(self) {
        use block::ConcreteBlock;
        use std::ops::Deref;

        // Construct the Mach service name without a trailing NUL for CString.
        let service_name = SERVICE_NAME.trim_end_matches('\0');
        let mach_port_name = match CString::new(service_name) {
            Ok(c) => c,
            Err(e) => {
                log::error!(target: LOG_TARGET, "Invalid XPC service name: {e}");
                return;
            }
        };

        log::info!(
            target: LOG_TARGET,
            "Waiting for XPC connections on {}",
            service_name
        );

        let rt = match tokio::runtime::Runtime::new() {
            Ok(rt) => Arc::new(rt),
            Err(e) => {
                log::error!(target: LOG_TARGET, "Failed to create Tokio runtime: {}", e);
                return;
            }
        };

        let connections: Subscribers = Arc::new(std::sync::Mutex::new(Vec::new()));
        {
            let receiver_arc = self.signal_receiver.clone();
            let conns = connections.clone();
            rt.spawn(async move {
                let mut receiver = receiver_arc
                    .lock()
                    .await
                    .take()
                    .expect("Signal receiver already taken");
                while let Some(signal) = receiver.recv().await {
                    log::trace!(target: LOG_TARGET, "Broadcasting signal: {:?}", signal);
                    let msg = super::util::signal_to_message(signal);
                    let xobj = message_to_xpc_object(msg);
                    let list = conns.lock().unwrap();
                    log::trace!(target: LOG_TARGET, "Active XPC clients: {}", list.len());
                    for c in list.iter() {
                        log::trace!(target: LOG_TARGET, "Sending signal to client");
                        unsafe { xpc_sys::xpc_connection_send_message(c.0, xobj) };
                    }
                    unsafe { xpc_sys::xpc_release(xobj) };
                }
            });
        }

        let service = unsafe {
            xpc_sys::xpc_connection_create_mach_service(
                mach_port_name.as_ptr(),
                ptr::null_mut(),
                xpc_sys::XPC_CONNECTION_MACH_SERVICE_LISTENER as u64,
            )
        };

        let agent = self.clone();
        let rt_accept = rt.clone();
        let conns_accept = connections.clone();
        let service_handler = ConcreteBlock::new(move |event: xpc_sys::xpc_object_t| {
            unsafe {
                let client = event as xpc_sys::xpc_connection_t;
                log::trace!(target: LOG_TARGET, "New XPC connection accepted");

                let agent_conn = agent.clone();
                let rt_conn = rt_accept.clone();
                let conns_for_handler = conns_accept.clone();
                let conn_handler = ConcreteBlock::new(move |msg: xpc_sys::xpc_object_t| {
                    match xpc_object_to_message(msg) {
                        Message::Dictionary(map) => {
                            let method = super::util::dict_get_str(&map, "method").or_else(|| super::util::dict_get_str(&map, "type")).unwrap_or_else(|| "<unknown>".to_string());
                            log::trace!(target: LOG_TARGET, "XPC request received: {}", method);
                            let result = rt_conn.block_on(super::rpc::dispatch(&agent_conn, &conns_for_handler, client, &map));
                            let reply = xpc_sys::xpc_dictionary_create_reply(msg);
                            if !reply.is_null() {
                                let payload = message_to_xpc_object(result.message);
                                let apply_block = ConcreteBlock::new(move |key: *const c_char, value: xpc_sys::xpc_object_t| {
                                    xpc_sys::xpc_dictionary_set_value(reply, key, value);
                                })
                                .copy();

                                xpc_sys::xpc_dictionary_apply(payload, apply_block.deref() as *const _ as *mut _);

                                xpc_sys::xpc_connection_send_message(client, reply);
                                xpc_sys::xpc_release(payload);
                                xpc_sys::xpc_release(reply);

                                // Drop any cleanup resource now that payload is constructed and sent.
                                drop(result.cleanup);
                                
                                log::trace!(target: LOG_TARGET, "XPC reply sent for method: {}", method);
                            } else {
                                log::warn!(target: LOG_TARGET, "No reply port for method: {}", method);
                            }
                        }
                        Message::Error(e) => {
                            match e {
                                MessageError::ConnectionInvalid => {
                                    let mut list = conns_for_handler.lock().unwrap();
                                    let before = list.len();
                                    list.retain(|c| c.0 != client);
                                    let after = list.len();
                                    if after < before {
                                        log::trace!(target: LOG_TARGET, "Removed closed XPC client from subscribers ({} -> {})", before, after);
                                    } else {
                                        log::debug!(target: LOG_TARGET, "XPC connection closed (no subscription)");
                                    }
                                }
                                other => {
                                    log::warn!(target: LOG_TARGET, "XPC error event: {:?}", other);
                                }
                            }
                        }
                        _ => {}
                    }
                })
                .copy();

                xpc_sys::xpc_connection_set_event_handler(
                    client,
                    conn_handler.deref() as *const _ as *mut _,
                );
                xpc_sys::xpc_connection_resume(client);
            }
        })
        .copy();

        unsafe {
            xpc_sys::xpc_connection_set_event_handler(
                service,
                service_handler.deref() as *const _ as *mut _,
            );
            xpc_sys::xpc_connection_resume(service);
        }

        futures_util::future::pending::<()>().await;
    }

    pub async fn send_event<T>(
        &self,
        make_event: impl FnOnce(kordophoned::daemon::events::Reply<T>) -> Event,
    ) -> DaemonResult<T> {
        let (tx, rx) = oneshot::channel();
        self.event_sink
            .send(make_event(tx))
            .await
            .map_err(|_| "Failed to send event")?;
        rx.await.map_err(|_| "Failed to receive reply".into())
    }
}
