package com.tomhempel.unityudp.data

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress

class UdpService {

    private var listenerSocket: DatagramSocket? = null
    @Volatile private var listening = false

    // ── Send ──────────────────────────────────────────────────────────────────

    suspend fun sendPackage(data: String, ipAddress: String, port: Int): Boolean =
        withContext(Dispatchers.IO) {
            try {
                val socket = DatagramSocket()
                val bytes = data.toByteArray(Charsets.UTF_8)
                val address = InetAddress.getByName(ipAddress)
                val packet = DatagramPacket(bytes, bytes.size, address, port)
                socket.send(packet)
                socket.close()
                true
            } catch (e: Exception) {
                false
            }
        }

    suspend fun sendBroadcast(data: String, port: Int): Boolean =
        withContext(Dispatchers.IO) {
            try {
                val socket = DatagramSocket()
                socket.broadcast = true
                val bytes = data.toByteArray(Charsets.UTF_8)
                val address = InetAddress.getByName("255.255.255.255")
                val packet = DatagramPacket(bytes, bytes.size, address, port)
                socket.send(packet)
                socket.close()
                true
            } catch (e: Exception) {
                false
            }
        }

    suspend fun sendAdvanced(
        data: String,
        port: Int,
        isBroadcast: Boolean,
        ipAddresses: List<String>
    ): Map<String, Boolean> = withContext(Dispatchers.IO) {
        when {
            isBroadcast -> mapOf("broadcast" to sendBroadcast(data, port))
            ipAddresses.isNotEmpty() -> ipAddresses.associateWith { ip ->
                sendPackage(data, ip, port)
            }
            else -> mapOf("127.0.0.1" to sendPackage(data, "127.0.0.1", port))
        }
    }

    // ── Listen ────────────────────────────────────────────────────────────────

    suspend fun startListening(
        port: Int,
        onReceive: (data: String, senderIp: String, senderPort: Int) -> Unit
    ) = withContext(Dispatchers.IO) {
        stopListening()
        try {
            val socket = DatagramSocket(port)
            socket.soTimeout = 0
            listenerSocket = socket
            listening = true
            val buffer = ByteArray(65535)
            while (listening) {
                try {
                    val packet = DatagramPacket(buffer, buffer.size)
                    socket.receive(packet)
                    val received = String(packet.data, 0, packet.length, Charsets.UTF_8)
                    val senderIp = packet.address.hostAddress ?: "unknown"
                    val senderPort = packet.port
                    onReceive(received, senderIp, senderPort)
                } catch (e: Exception) {
                    if (!listening) break
                }
            }
        } catch (e: Exception) {
            // socket failed to bind or was closed
        } finally {
            listening = false
        }
    }

    fun stopListening() {
        listening = false
        listenerSocket?.close()
        listenerSocket = null
    }

    val isListening get() = listening
}
