package com.tsmobile.app

import teamspeak.Client
import teamspeak.EventCallback
import teamspeak.TSClient
import teamspeak.Teamspeak
import teamspeak.TextMsg
import java.lang.reflect.Proxy

/**
 * TeamSpeak Bridge 封装
 *
 * 将 gomobile 导出的 teamspeak 包封装为 Kotlin 友好的 API，
 * 使用回调接口传递事件给 ViewModel 层。
 *
 * 注意：gomobile 不支持返回 []string、[]*T 等复杂切片类型，
 * 频道列表和客户端列表通过 JSON 字符串传递。
 */
object TSBridge {

    private val connectLock = Any()
    private var client: TSClient? = null
    private var connectionGeneration: Long = 0
    private var currentIdentity: String = ""

    private fun beginConnection(identity: String): Pair<TSClient, Long> = synchronized(connectLock) {
        client?.disconnect()
        client = null
        connectionGeneration += 1
        currentIdentity = identity
        val tsClient = Teamspeak.newClient()
        client = tsClient
        tsClient to connectionGeneration
    }

    private fun endConnection(tsClient: TSClient) = synchronized(connectLock) {
        if (client === tsClient) {
            client = null
        }
    }

    private fun captureGeneration(): Long = synchronized(connectLock) { connectionGeneration }

    fun currentGeneration(): Long = synchronized(connectLock) { connectionGeneration }

    fun currentIdentity(): String = synchronized(connectLock) { currentIdentity }

    /** Poke 事件（Kotlin 侧数据类，对应 Go PokeEvent） */
    data class PokeEventData(
        val invokerID: Int = 0,
        val invokerName: String = "",
        val invokerUID: String = "",
        val message: String = "",
    )

    /** 事件回调 */
    interface Callbacks {
        fun onConnected()
        fun onDisconnected(message: String)
        fun onTextMessage(msg: TextMsg)
        fun onClientEnter(client: Client)
        fun onClientLeave(id: Long, reasonMsg: String)
        fun onClientMoved(id: Long, targetChannelID: String)
        fun onKicked(reason: String)
        /** A 20 ms, 48 kHz interleaved stereo PCM16 little-endian frame mixed by Go. */
        fun onMixedVoicePCM(pcm: ByteArray)
        /** Go-owned speaking transition; Kotlin must not infer this from PCM. */
        fun onClientSpeaking(clientID: Long, speaking: Boolean)
        fun onPoked(event: PokeEventData)
    }

    /**
     * 连接到 TeamSpeak 服务器
     * @return 空字符串表示成功，非空为错误信息
     */
    fun connect(
        host: String,
        nickname: String,
        password: String = "",
        defaultChannel: String = "",
        defaultChannelPassword: String = "",
        callbacks: Callbacks,
    ): String {
        val identity = currentIdentity
        val (tsClient, generation) = beginConnection(identity)
        val error = doConnect(tsClient, generation, callbacks) { proxy ->
            tsClient.connect(host, nickname, password, defaultChannel, defaultChannelPassword, proxy)
        }
        if (error.isNotEmpty()) endConnection(tsClient)
        return error
    }

    fun connectWithIdentity(
        identity: String,
        host: String,
        nickname: String,
        password: String = "",
        defaultChannel: String = "",
        defaultChannelPassword: String = "",
        callbacks: Callbacks,
    ): String {
        val (tsClient, generation) = beginConnection(identity)
        val error = doConnect(tsClient, generation, callbacks) { proxy ->
            tsClient.connectWithIdentity(
                identity,
                host,
                nickname,
                password,
                defaultChannel,
                defaultChannelPassword,
                proxy,
            )
        }
        if (error.isNotEmpty()) endConnection(tsClient)
        return error
    }

    private fun doConnect(
        tsClient: TSClient,
        generation: Long,
        callbacks: Callbacks,
        connect: (EventCallback) -> String,
    ): String {
        val callbackProxy = Proxy.newProxyInstance(
            EventCallback::class.java.classLoader,
            arrayOf(EventCallback::class.java),
        ) { _, method, args ->
            if (captureGeneration() != generation) {
                return@newProxyInstance null
            }
            val values = args ?: emptyArray()
            when (method.name) {
                "onConnected" -> callbacks.onConnected()
                "onDisconnected" -> callbacks.onDisconnected(values[0] as String)
                "onTextMessage" -> (values[0] as? TextMsg)?.let(callbacks::onTextMessage)
                "onClientEnter" -> (values[0] as? Client)?.let(callbacks::onClientEnter)
                "onClientLeave" -> callbacks.onClientLeave((values[0] as Number).toLong(), values[1] as String)
                "onClientMoved" -> callbacks.onClientMoved((values[0] as Number).toLong(), values[1] as String)
                "onKicked" -> callbacks.onKicked(values[0] as String)
                "onMixedVoicePCM" -> (values[0] as? ByteArray)?.let {
                    android.util.Log.d("TSVoice", "Kotlin mixed PCM bytes=${it.size}")
                    callbacks.onMixedVoicePCM(it)
                }
                "onClientSpeaking" -> {
                    android.util.Log.i("TSVoice", "Kotlin speaking client=${values[0]} speaking=${values[1]}")
                    callbacks.onClientSpeaking((values[0] as Number).toLong(), values[1] as Boolean)
                }
                "onPoked" -> (values[0] as? teamspeak.PokeEvent)?.let { event ->
                    callbacks.onPoked(PokeEventData(
                        invokerID = event.invokerID.toInt(),
                        invokerName = event.invokerName,
                        invokerUID = event.invokerUID,
                        message = event.message,
                    ))
                }
                "onVoiceData" -> Unit
                "toString" -> "TSBridge.EventCallback"
                "hashCode" -> System.identityHashCode(callbacks)
                "equals" -> false
                else -> android.util.Log.w("TSBridge", "Ignoring unknown bridge callback ${method.name}")
            }
            null
        } as EventCallback

        return try {
            connect(callbackProxy)
        } catch (t: Throwable) {
            android.util.Log.w("TSBridge", "connect threw", t)
            "连接失败"
        }
    }

    /** 断开连接 */
    fun disconnect() {
        synchronized(connectLock) {
            client?.disconnect()
            client = null
            connectionGeneration += 1
        }
    }

    /** 是否已连接 */
    fun isConnected(): Boolean = client?.isConnected ?: false

    /** Go-owned receive-audio controls, available after installing the migrated AAR. */
    fun setRemoteClientMuted(clientID: Int, muted: Boolean) {
        invokeReceiveAudio("setRemoteClientMuted", clientID.toLong(), muted)
    }

    fun removeRemoteAudioClient(clientID: Int) {
        invokeReceiveAudio("removeRemoteAudioClient", clientID.toLong())
    }

    fun clearRemoteAudioClients() {
        invokeReceiveAudio("clearRemoteAudioClients")
    }

    fun startReceiveAudio() {
        invokeReceiveAudio("startReceiveAudio")
    }

    fun stopReceiveAudio() {
        invokeReceiveAudio("stopReceiveAudio")
    }

    private fun invokeReceiveAudio(name: String, vararg args: Any) {
        val target = client ?: return
        val method = target.javaClass.methods.firstOrNull { candidate ->
            candidate.name == name && candidate.parameterTypes.size == args.size
        } ?: run {
            android.util.Log.w("TSBridge", "$name is unavailable; install the migrated teamspeak.aar")
            return
        }
        runCatching { method.invoke(target, *args) }
            .onFailure { android.util.Log.w("TSBridge", "$name failed", it) }
    }

    /**
     * 获取频道列表（JSON 格式）
     * 返回 JSON 数组字符串，例如：
     * [{"id":"1","name":"Channel1","parentId":"0","description":""}]
     */
    fun getChannelsJSON(): String = client?.getChannelsJSON() ?: "[]"

    /**
     * 获取在线客户端列表（JSON 格式）
     * 返回 JSON 数组字符串，例如：
     * [{"id":1,"nickname":"User","uid":"xxx","channelId":"1","serverGroups":[],"isSelf":true}]
     */
    fun getClientsJSON(): String = client?.getClientsJSON() ?: "[]"

    /** 获取当前客户端 ID */
    fun getClientID(): Long = client?.getClientID()?.toLong() ?: 0L

    /** 获取当前频道 ID */
    fun getChannelID(): String = client?.getChannelID() ?: "0"

    /**
     * 发送频道文字消息
     * @return 空字符串表示成功
     */
    fun sendChannelMessage(channelID: String, message: String): String =
        client?.sendChannelMessage(channelID, message) ?: "未连接"

    /**
     * 发送文字消息
     * @param targetMode 1=私聊, 2=频道, 3=服务器
     * @return 空字符串表示成功
     */
    fun sendTextMessage(targetMode: Int, targetId: String, message: String): String {
        // TODO: 重新编译 AAR 后使用 client?.sendTextMessage(targetMode, targetId, message)
        // 当前 AAR 只有 sendChannelMessage，对应 targetMode=2
        return when (targetMode) {
            2 -> sendChannelMessage(targetId, message)
            else -> "暂不支持该消息类型"
        }
    }

    /**
     * 移动到指定频道
     * @return 空字符串表示成功
     */
    fun moveToChannel(channelID: String, password: String = ""): String =
        client?.moveToChannel(channelID, password) ?: "未连接"

    /**
     * 发送 Opus 语音帧
     * @param data Opus 编码的音频数据
     * @param codec 编码类型：4=Opus Voice, 5=Opus Music
     * @return 空字符串表示成功
     */
    fun sendVoice(data: ByteArray, codec: Long = 4): String =
        client?.sendVoice(data, codec) ?: "未连接"

    // ── 以下方法需要重新编译 AAR 以匹配 Go 侧实现 ──

    /**
     * 向指定客户端发送 Poke
     * @return 空字符串表示成功
     */
    fun poke(clientID: String, message: String): String =
        client?.poke(clientID, message) ?: "未连接"

    /**
     * 获取服务器信息（JSON 格式）
     * 返回 JSON 对象字符串
     */
    fun getServerInfoJSON(): String =
        client?.getServerInfoJSON() ?: "{}"

    /**
     * 获取频道详细信息（JSON 格式）
     * @param channelID 频道 ID 字符串
     * 返回 JSON 对象字符串
     */
    fun getChannelDetailInfoJSON(channelID: String): String =
        client?.getChannelDetailInfoJSON(channelID) ?: "{}"

    /**
     * 踢出客户端
     * @param clientID 客户端 ID
     * @param reasonID 4=频道踢出, 5=服务器踢出
     * @param reasonMsg 踢出原因
     * @return 空字符串表示成功
     */
    fun kickClient(clientID: String, reasonID: Int, reasonMsg: String): String {
        val id = clientID.toIntOrNull() ?: return "无效的客户端 ID"
        return client?.kickClient(id.toLong(), reasonID.toLong(), reasonMsg) ?: "未连接"
    }

    /**
     * 下载频道文件并返回 base64 编码的内容。
     * @param channelID 频道 ID
     * @param path 文件虚拟路径（如 "/image.png"）
     * @return JSON: {"data":"base64...","size":12345}，失败返回 "{}"
     */
    fun downloadFileBytes(channelID: String, path: String): String =
        client?.downloadFileBytesJSON(channelID, path) ?: "{}"
}
