use std::{
    ffi::c_char,
    ptr::{self, null, null_mut},
    str::FromStr,
};

use pawkit_net::signaling::model::{ChannelConfiguration, HostId};
use pawkit_net::{NetClientPeerEvent, NetHostPeerEvent, SimpleNetClientPeer, SimpleNetHostPeer};

use crate::{
    c_enum, cstr_to_str, disown_str_to_cstr, drop_from_heap, move_to_heap, ptr_to_ref,
    ptr_to_ref_mut, ptr_to_slice,
};

#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct CChannelConfig {
    ordered: bool,
    retries: u16,
}

impl Into<ChannelConfiguration> for CChannelConfig {
    fn into(self) -> ChannelConfiguration {
        return ChannelConfiguration {
            ordered: self.ordered,
            reliability: if self.retries == u16::MAX {
                None
            } else {
                Some(self.retries.try_into().unwrap())
            },
        };
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_peer_create(
    server_url: *const c_char,
    server_url_len: usize,
    game_id: u32,
    request_proxy: bool,
    channels: *const CChannelConfig,
    channels_size: usize,
) -> *mut SimpleNetHostPeer {
    unsafe {
        let Some(server_url) = cstr_to_str(server_url, server_url_len) else {
            return null_mut();
        };

        let Some(channels) = ptr_to_slice(channels, channels_size) else {
            return null_mut();
        };

        return move_to_heap(SimpleNetHostPeer::create(
            server_url,
            game_id,
            request_proxy,
            channels
                .iter()
                .map(|config: &CChannelConfig| CChannelConfig::into(*config))
                .collect(),
        ));
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_peer_free(peer: *mut SimpleNetHostPeer) {
    unsafe {
        if peer.is_null() {
            return;
        }

        drop_from_heap(peer);
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_peer_get_host_id(
    peer: *mut SimpleNetHostPeer,
    len: *mut usize,
) -> *const c_char {
    unsafe {
        if peer.is_null() {
            return ptr::null();
        }

        let Some(len) = ptr_to_ref_mut(len) else {
            return null_mut();
        };

        let peer = &*peer;

        return disown_str_to_cstr(&peer.get_host_id().to_string(), len);
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_peer_send_packet(
    peer: *mut SimpleNetHostPeer,
    client_id: usize,
    channel: usize,
    data: *const u8,
    size: usize,
) {
    unsafe {
        let Some(peer) = ptr_to_ref(peer) else {
            return;
        };

        let Some(data) = ptr_to_slice(data, size) else {
            return;
        };

        peer.send_packet(client_id, channel, data);
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_poll_event(
    peer: *mut SimpleNetHostPeer,
) -> *mut NetHostPeerEvent {
    unsafe {
        if peer.is_null() {
            return null_mut();
        }

        let Some(evt) = (*peer).next_event() else {
            return null_mut();
        };

        return move_to_heap(evt);
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_event_free(evt: *mut NetHostPeerEvent) {
    unsafe {
        if evt.is_null() {
            return;
        }

        drop_from_heap(evt);
    }
}

c_enum!(CHostPeerEventType : u8 {
    HOST_PEER_CONNECTED,
    HOST_PEER_DISCONNECTED,
    HOST_PACKET_RECEIVED,
    HOST_ID_UPDATED,
});

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_event_get_type(
    evt: *mut NetHostPeerEvent,
) -> CHostPeerEventType {
    unsafe {
        if evt.is_null() {
            return 255;
        }

        return match &*evt {
            NetHostPeerEvent::PeerConnected { peer_id: _ } => HOST_PEER_CONNECTED,
            NetHostPeerEvent::PeerDisconnected { peer_id: _ } => HOST_PEER_DISCONNECTED,
            NetHostPeerEvent::PacketReceived {
                peer_id: _,
                data: _,
                channel: _,
            } => HOST_PACKET_RECEIVED,
            NetHostPeerEvent::HostIdUpdated => HOST_ID_UPDATED,
        };
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_event_get_peer_id(evt: *mut NetHostPeerEvent) -> usize {
    unsafe {
        if evt.is_null() {
            return usize::MAX;
        }

        return match &*evt {
            NetHostPeerEvent::PeerConnected { peer_id } => *peer_id,
            NetHostPeerEvent::PeerDisconnected { peer_id } => *peer_id,
            NetHostPeerEvent::PacketReceived {
                peer_id,
                data: _,
                channel: _,
            } => *peer_id,
            NetHostPeerEvent::HostIdUpdated => usize::MAX,
        };
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_host_event_get_data(
    evt: *mut NetHostPeerEvent,
    len: *mut usize,
) -> *const u8 {
    unsafe {
        if evt.is_null() || len.is_null() {
            return null();
        }

        let NetHostPeerEvent::PacketReceived {
            peer_id: _,
            data,
            channel: _,
        } = &*evt
        else {
            return null();
        };

        *len = data.len();

        return data.as_ptr();
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_client_peer_create(
    host_id: *const c_char,
    host_id_len: usize,
    game_id: u32,
) -> *mut SimpleNetClientPeer {
    unsafe {
        let Some(host_id) = cstr_to_str(host_id, host_id_len) else {
            return null_mut();
        };

        let Ok(host_id) = HostId::from_str(host_id) else {
            return null_mut();
        };

        return move_to_heap(SimpleNetClientPeer::create(game_id, host_id));
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_client_peer_free(peer: *mut SimpleNetClientPeer) {
    unsafe {
        if peer.is_null() {
            return;
        }

        drop_from_heap(peer);
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_client_peer_send_packet(
    peer: *mut SimpleNetClientPeer,
    channel: usize,
    data: *const u8,
    size: usize,
) {
    unsafe {
        let Some(peer) = ptr_to_ref(peer) else {
            return;
        };

        let Some(data) = ptr_to_slice(data, size) else {
            return;
        };

        peer.send_packet(channel, data);
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_client_peer_poll_event(
    peer: *mut SimpleNetClientPeer,
) -> *mut NetClientPeerEvent {
    unsafe {
        if peer.is_null() {
            return null_mut();
        }

        let Some(evt) = (*peer).next_event() else {
            return null_mut();
        };

        return move_to_heap(evt);
    }
}

c_enum!(CClientPeerEventType : u8 {
    CLIENT_CONNECTED,
    CLIENT_DISCONNECTED,
    CLIENT_CONNECTION_FAILED,
    CLIENT_PACKET_RECEIVED,
});

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_client_event_get_type(
    evt: *mut NetClientPeerEvent,
) -> CClientPeerEventType {
    unsafe {
        if evt.is_null() {
            return 255;
        }

        return match &*evt {
            NetClientPeerEvent::Connected => CLIENT_CONNECTED,
            NetClientPeerEvent::Disconnected => CLIENT_DISCONNECTED,
            NetClientPeerEvent::ConnectionFailed => CLIENT_CONNECTION_FAILED,
            NetClientPeerEvent::PacketReceived {
                data: _,
                channel: _,
            } => CLIENT_PACKET_RECEIVED,
        };
    }
}

#[unsafe(no_mangle)]
unsafe extern "C" fn pawkit_net_client_event_get_data(
    evt: *mut NetClientPeerEvent,
    len: *mut usize,
) -> *const u8 {
    unsafe {
        if evt.is_null() || len.is_null() {
            return null();
        }

        let NetClientPeerEvent::PacketReceived { data, channel: _ } = &*evt else {
            return null();
        };

        *len = data.len();

        return data.as_ptr();
    }
}
