use mlua::prelude::*;
use pawkit_net::{NetClientPeerEvent, NetHostPeerEvent, SimpleNetClientPeer, SimpleNetHostPeer};
use pawkit_net_signaling::model::HostId;

use crate::lua_enum;

lua_enum!(host_events {
    PeerConnected = LuaNetHostPeerEvent::PEER_CONNECTED,
    PeerDisconnected = LuaNetHostPeerEvent::PEER_DISCONNECTED,
    PacketReceived = LuaNetHostPeerEvent::PACKET_RECEIVED,
    HostIdUpdated = LuaNetHostPeerEvent::HOST_ID_UPDATED,
});

lua_enum!(client_events {
    Connected = LuaNetClientPeerEvent::CONNECTED,
    Disconnected = LuaNetClientPeerEvent::DISCONNECTED,
    ConnectionFailed = LuaNetClientPeerEvent::CONNECTION_FAILED,
    PacketReceived = LuaNetClientPeerEvent::PACKET_RECEIVED,
});

pub(crate) fn init(lua: &Lua) -> LuaResult<LuaTable> {
    let exports = lua.create_table()?;

    exports.set("host", lua.create_function(host)?)?;

    exports.set("host_events", host_events(lua)?)?;

    exports.set("connect", lua.create_function(connect)?)?;

    exports.set("client_events", client_events(lua)?)?;

    return Ok(exports);
}

fn host(lua: &Lua, args: (String, u32, Option<bool>, LuaValue)) -> LuaResult<LuaNetHostPeer> {
    return Ok(LuaNetHostPeer {
        peer: SimpleNetHostPeer::create(
            &args.0,
            args.1,
            args.2.unwrap_or(false),
            lua.from_value(args.3)?,
        ),
    });
}

fn connect(_lua: &Lua, args: (u32, String)) -> LuaResult<LuaNetClientPeer> {
    let game_id = args.0;
    let host_id_str = args.1;

    let host_id: HostId = host_id_str
        .parse()
        .map_err(|e| LuaError::external(format!("Invalid host ID: {}", e)))?;

    let peer = SimpleNetClientPeer::create(game_id, host_id);

    Ok(LuaNetClientPeer { peer })
}

pub struct LuaNetHostPeer {
    peer: SimpleNetHostPeer,
}

impl LuaNetHostPeer {
    fn send_packet(_lua: &Lua, this: &Self, args: (usize, usize, LuaString)) -> LuaResult<()> {
        this.peer.send_packet(args.0, args.1, &args.2.as_bytes());

        return Ok(());
    }

    fn shutdown(_lua: &Lua, this: &Self, _args: ()) -> LuaResult<()> {
        this.peer.shutdown();

        return Ok(());
    }

    fn next_event(
        _lua: &Lua,
        this: &mut Self,
        _args: (),
    ) -> LuaResult<Option<LuaNetHostPeerEvent>> {
        let Some(evt) = this.peer.next_event() else {
            return Ok(None);
        };

        return Ok(Some(LuaNetHostPeerEvent { evt }));
    }

    fn get_host_id(_lua: &Lua, this: &mut Self, _args: ()) -> LuaResult<String> {
        return Ok(this.peer.get_host_id().to_string());
    }
}

impl LuaUserData for LuaNetHostPeer {
    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("send_packet", Self::send_packet);
        methods.add_method("shutdown", Self::shutdown);
        methods.add_method_mut("next_event", Self::next_event);
        methods.add_method_mut("get_host_id", Self::get_host_id);
    }
}

struct LuaNetHostPeerEvent {
    evt: NetHostPeerEvent,
}

impl LuaNetHostPeerEvent {
    const PEER_CONNECTED: i32 = 0;
    const PEER_DISCONNECTED: i32 = 1;
    const PACKET_RECEIVED: i32 = 2;
    const HOST_ID_UPDATED: i32 = 3;

    fn get_type(_lua: &Lua, this: &Self, _args: ()) -> LuaResult<i32> {
        return Ok(match this.evt {
            NetHostPeerEvent::PeerConnected { peer_id: _ } => Self::PEER_CONNECTED,
            NetHostPeerEvent::PeerDisconnected { peer_id: _ } => Self::PEER_DISCONNECTED,
            NetHostPeerEvent::PacketReceived {
                peer_id: _,
                data: _,
                channel: _,
            } => Self::PACKET_RECEIVED,
            NetHostPeerEvent::HostIdUpdated => Self::HOST_ID_UPDATED,
        });
    }

    fn get_peer_id(_lua: &Lua, this: &Self, _args: ()) -> LuaResult<Option<usize>> {
        return Ok(match this.evt {
            NetHostPeerEvent::PeerConnected { peer_id } => Some(peer_id),
            NetHostPeerEvent::PeerDisconnected { peer_id } => Some(peer_id),
            NetHostPeerEvent::PacketReceived {
                peer_id,
                data: _,
                channel: _,
            } => Some(peer_id),
            NetHostPeerEvent::HostIdUpdated => None,
        });
    }

    fn get_data(lua: &Lua, this: &Self, _args: ()) -> LuaResult<Option<LuaString>> {
        let NetHostPeerEvent::PacketReceived {
            peer_id: _,
            data,
            channel: _,
        } = &this.evt
        else {
            return Ok(None);
        };

        return Ok(Some(lua.create_string(data)?));
    }

    fn get_channel(_lua: &Lua, this: &Self, _args: ()) -> LuaResult<Option<usize>> {
        let NetHostPeerEvent::PacketReceived {
            peer_id: _,
            data: _,
            channel,
        } = &this.evt
        else {
            return Ok(None);
        };

        return Ok(Some(*channel));
    }
}

impl LuaUserData for LuaNetHostPeerEvent {
    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("get_type", Self::get_type);
        methods.add_method("get_peer_id", Self::get_peer_id);
        methods.add_method("get_data", Self::get_data);
        methods.add_method("get_channel", Self::get_channel);
    }
}

pub struct LuaNetClientPeer {
    peer: SimpleNetClientPeer,
}

impl LuaNetClientPeer {
    fn send_packet(_lua: &Lua, this: &Self, args: (usize, LuaString)) -> LuaResult<()> {
        this.peer.send_packet(args.0, &args.1.as_bytes());

        return Ok(());
    }

    fn next_event(
        _lua: &Lua,
        this: &mut Self,
        _args: (),
    ) -> LuaResult<Option<LuaNetClientPeerEvent>> {
        let Some(evt) = this.peer.next_event() else {
            return Ok(None);
        };

        return Ok(Some(LuaNetClientPeerEvent { evt }));
    }

    fn disconnect(_lua: &Lua, this: &Self, _args: ()) -> LuaResult<()> {
        this.peer.disconnect();

        return Ok(());
    }
}

impl LuaUserData for LuaNetClientPeer {
    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("send_packet", Self::send_packet);
        methods.add_method_mut("next_event", Self::next_event);
        methods.add_method("disconnect", Self::disconnect);
    }
}

struct LuaNetClientPeerEvent {
    evt: NetClientPeerEvent,
}

impl LuaNetClientPeerEvent {
    const CONNECTED: i32 = 0;
    const DISCONNECTED: i32 = 1;
    const CONNECTION_FAILED: i32 = 2;
    const PACKET_RECEIVED: i32 = 3;

    fn get_type(_lua: &Lua, this: &Self, _args: ()) -> LuaResult<i32> {
        return Ok(match this.evt {
            NetClientPeerEvent::Connected => Self::CONNECTED,
            NetClientPeerEvent::Disconnected => Self::DISCONNECTED,
            NetClientPeerEvent::ConnectionFailed => Self::CONNECTION_FAILED,
            NetClientPeerEvent::PacketReceived {
                data: _,
                channel: _,
            } => Self::PACKET_RECEIVED,
        });
    }

    fn get_data(lua: &Lua, this: &Self, _args: ()) -> LuaResult<Option<LuaString>> {
        let NetClientPeerEvent::PacketReceived { data, channel: _ } = &this.evt else {
            return Ok(None);
        };

        return Ok(Some(lua.create_string(data)?));
    }
}

impl LuaUserData for LuaNetClientPeerEvent {
    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("get_type", Self::get_type);
        methods.add_method("get_data", Self::get_data);
    }
}
