package com.example.m20_gamepad.network

import com.example.m20_gamepad.network.models.ControlCommands
import com.example.m20_gamepad.network.models.StatusReport
import com.example.m20_gamepad.network.models.parseStatusReport
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import java.net.SocketTimeoutException

/**
 * UDP 协议客户端。管理连接、心跳、指令发送和状态上报接收。
 *
 * 用法：
 * ```kotlin
 * val client = ProtocolClient(host, port, scope)
 * client.onStatusReport.collect { report -> /* 处理上报 */ }
 * client.connect()
 * client.sendCommand(ControlCommands.axisCommand(x = 0.5, yaw = 0.0))
 * ```
 */
class ProtocolClient(
    /** 机器人 IP 地址 */
    private val host: String,
    /** 机器人 UDP 端口 */
    private val port: Int,
    /** 协程作用域，用于启动心跳和接收协程 */
    private val scope: CoroutineScope
) {
    /** 连接状态 */
    enum class State {
        /** 未连接 */
        DISCONNECTED,
        /** 连接中（socket 已创建，正在发送订阅） */
        CONNECTING,
        /** 已连接（心跳和接收正常运行） */
        CONNECTED,
        /** 断线（超过 3 秒无数据） */
        TIMEOUT,
        /** 连接出错 */
        ERROR
    }

    private val _state = MutableStateFlow(State.DISCONNECTED)
    val state: StateFlow<State> = _state.asStateFlow()

    /** 状态上报事件流 */
    private val _statusReport = MutableSharedFlow<StatusReport>(
        replay = 0,
        extraBufferCapacity = 16,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val onStatusReport: SharedFlow<StatusReport> = _statusReport.asSharedFlow()

    private val encoder = PacketEncoder()
    private var socket: DatagramSocket? = null
    private var remoteAddress: InetAddress? = null

    // 上次收到数据的时间戳（用于断线判定）
    @Volatile
    private var lastPacketTimeMs = 0L

    // 协程引用
    private var heartbeatJob: Job? = null
    private var receiveJob: Job? = null
    private var timeoutJob: Job? = null

    // 指令发送通道（非阻塞，用于从 UI 线程发送指令）
    private var sendChannel: Channel<ByteArray> = Channel(Channel.BUFFERED)
    private var sendJob: Job? = null

    /** 是否已连接 */
    val isConnected: Boolean get() = _state.value == State.CONNECTED

    /**
     * 建立连接：
     * 1. 创建 UDP socket
     * 2. 启动接收协程
     * 3. 发送订阅请求
     * 4. 启动心跳和断线检测
     */
    fun connect() {
        if (_state.value != State.DISCONNECTED && _state.value != State.ERROR) return

        // 重建发送通道（上次 disconnect 可能已关闭）
        // 使用自定义 Channel 替代重新赋值，因为 Channel 在 close 后不可复用
        _state.value = State.CONNECTING
        try {
            val address = InetAddress.getByName(host)
            remoteAddress = address

            val sock = DatagramSocket()
            sock.soTimeout = 5000
            socket = sock

            lastPacketTimeMs = System.currentTimeMillis()

            // 重新创建 sendChannel（旧的可能已关闭）
            sendChannel = Channel(Channel.BUFFERED)

            // 启动接收协程
            receiveJob = scope.launch(Dispatchers.IO) {
                receiveLoop(sock)
            }

            // 启动发送协程（从 Channel 消费）
            sendJob = scope.launch(Dispatchers.IO) {
                for (packet in sendChannel) {
                    try {
                        sock.send(DatagramPacket(packet, packet.size, address, port))
                    } catch (_: Exception) {
                        // 发送失败由断线检测处理
                    }
                }
            }

            // 发送订阅请求（触发服务端 UDP 推送）
            sendSubscriptionRequests()

            _state.value = State.CONNECTED

            // 启动心跳
            heartbeatJob = scope.launch(Dispatchers.IO) {
                heartbeatLoop(sock, address)
            }

            // 启动断线检测
            timeoutJob = scope.launch {
                timeoutCheckLoop()
            }
        } catch (e: Exception) {
            _state.value = State.ERROR
            closeSocket()
        }
    }

    /**
     * 断开连接，清理所有资源。
     */
    fun disconnect() {
        _state.value = State.DISCONNECTED
        heartbeatJob?.cancel()
        heartbeatJob = null
        receiveJob?.cancel()
        receiveJob = null
        sendJob?.cancel()
        sendJob = null
        timeoutJob?.cancel()
        timeoutJob = null
        sendChannel.close()
        closeSocket()
        // 重新创建 sendChannel 以便下次 connect 使用
    }

    /**
     * 发送控制指令（挂起函数，发送到 Channel 后会立即返回）。
     * 如果未连接，指令会被丢弃。
     */
    suspend fun sendCommand(asduJson: String) {
        if (_state.value != State.CONNECTED) return
        val packet = encoder.encode(asduJson)
        sendChannel.send(packet)
    }

    /**
     * 发送控制指令（非挂起版本，用于 UI 线程等非协程环境）。
     * 如果 Channel 已满则丢弃。
     */
    fun sendCommandBlocking(asduJson: String) {
        if (_state.value != State.CONNECTED) return
        val packet = encoder.encode(asduJson)
        sendChannel.trySend(packet)
    }

    // ========== 内部实现 ==========

    private fun closeSocket() {
        try {
            socket?.close()
        } catch (_: Exception) { }
        socket = null
    }

    /** 发送订阅请求 */
    private fun sendSubscriptionRequests() {
        val subscriptions: List<Pair<Int, Int>> = listOf(
            ControlCommands.SUB_BASIC,
            ControlCommands.SUB_MOTION_CONTROL,
            ControlCommands.SUB_DEVICE,
            ControlCommands.SUB_ERROR
        )
        val sock = socket ?: return
        val addr = remoteAddress ?: return
        for ((type, cmd) in subscriptions) {
            try {
                val json = ControlCommands.subscribeStatus(type, cmd)
                val packet = encoder.encode(json)
                sock.send(DatagramPacket(packet, packet.size, addr, port))
            } catch (_: Exception) { }
        }
    }

    /** 接收循环 */
    private fun CoroutineScope.receiveLoop(sock: DatagramSocket) {
        val buffer = ByteArray(65535)
        while (isActive && _state.value != State.DISCONNECTED) {
            try {
                val packet = DatagramPacket(buffer, buffer.size)
                sock.receive(packet)

                lastPacketTimeMs = System.currentTimeMillis()

                val data = packet.data.copyOfRange(0, packet.length)
                val message = PacketDecoder.decode(data) ?: continue

                // 解析 ASDU JSON
                val report = parseStatusReport(message.asdu)
                if (report != null) {
                    _statusReport.tryEmit(report)
                }
            } catch (_: SocketTimeoutException) {
                // 超时正常，继续循环
                continue
            } catch (_: Exception) {
                if (_state.value != State.DISCONNECTED) {
                    _state.value = State.ERROR
                }
                break
            }
        }
    }

    /** 心跳循环（1 Hz） */
    private suspend fun CoroutineScope.heartbeatLoop(sock: DatagramSocket, address: InetAddress) {
        while (isActive && _state.value == State.CONNECTED) {
            try {
                val json = ControlCommands.heartbeat()
                val packet = encoder.encode(json)
                sock.send(DatagramPacket(packet, packet.size, address, port))
            } catch (_: Exception) {
                break
            }
            delay(1000) // 1 Hz
        }
    }

    /** 断线检测：3 秒无数据 → 标记 TIMEOUT */
    private suspend fun CoroutineScope.timeoutCheckLoop() {
        while (isActive && _state.value != State.DISCONNECTED) {
            val elapsed = System.currentTimeMillis() - lastPacketTimeMs
            if (elapsed > 3000 && _state.value == State.CONNECTED) {
                _state.value = State.TIMEOUT
            }
            delay(500)
        }
    }
}