package com.omixlab.lckcontrol.data.repository

import android.util.Log
import com.omixlab.lckcontrol.data.local.TokenStore
import com.omixlab.lckcontrol.data.remote.ChatConnectionStatus
import com.omixlab.lckcontrol.data.remote.ChatMessage
import com.omixlab.lckcontrol.data.remote.ChatMessageEvent
import com.omixlab.lckcontrol.data.remote.ChatStatusEvent
import com.omixlab.lckcontrol.data.remote.SendChatCommand
import com.omixlab.lckcontrol.data.remote.SubscribeCommand
import com.omixlab.lckcontrol.data.remote.UnsubscribeCommand
import com.squareup.moshi.Moshi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
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.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ChatRepository @Inject constructor(
    private val okHttpClient: OkHttpClient,
    private val tokenStore: TokenStore,
    private val moshi: Moshi,
) {
    companion object {
        private const val TAG = "ChatRepository"
        private const val WS_BASE_URL = "wss://lck.omigame.dev/chat/ws"
        private const val MAX_MESSAGES_PER_DEST = 500
        private const val MAX_RECONNECT_DELAY_MS = 30_000L
    }

    // Separate client for WebSocket with ping keepalive and no read timeout
    private val wsClient = okHttpClient.newBuilder()
        .pingInterval(30, TimeUnit.SECONDS)
        .readTimeout(0, TimeUnit.MILLISECONDS)
        .build()

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    // key: "planId:SERVICE:destinationId"
    private val _messages = MutableStateFlow<Map<String, List<ChatMessage>>>(emptyMap())
    val messages: StateFlow<Map<String, List<ChatMessage>>> = _messages.asStateFlow()

    private val _connectionStatus = MutableStateFlow<Map<String, ChatConnectionStatus>>(emptyMap())
    val connectionStatus: StateFlow<Map<String, ChatConnectionStatus>> = _connectionStatus.asStateFlow()

    private val _unreadCounts = MutableStateFlow<Map<String, Int>>(emptyMap())
    val unreadCounts: StateFlow<Map<String, Int>> = _unreadCounts.asStateFlow()

    private val _latestMessage = MutableSharedFlow<ChatMessageEvent>(extraBufferCapacity = 64)
    val latestMessage: SharedFlow<ChatMessageEvent> = _latestMessage.asSharedFlow()

    private var webSocket: WebSocket? = null
    private var activeViewKey: String? = null
    private var reconnectAttempt = 0
    private var shouldConnect = false
    private val activeSubscriptions = mutableSetOf<String>()

    private val messageAdapter = moshi.adapter(ChatMessageEvent::class.java)
    private val statusAdapter = moshi.adapter(ChatStatusEvent::class.java)
    private val subscribeAdapter = moshi.adapter(SubscribeCommand::class.java)
    private val unsubscribeAdapter = moshi.adapter(UnsubscribeCommand::class.java)
    private val sendAdapter = moshi.adapter(SendChatCommand::class.java)

    fun connect() {
        shouldConnect = true
        doConnect()
    }

    fun disconnect() {
        shouldConnect = false
        activeSubscriptions.clear()
        webSocket?.close(1000, "App disconnecting")
        webSocket = null
    }

    fun subscribe(planId: String) {
        if (!activeSubscriptions.add(planId)) return // already subscribed
        val cmd = SubscribeCommand(planId = planId)
        val sent = webSocket?.send(subscribeAdapter.toJson(cmd))
        Log.d(TAG, "subscribe($planId) wsConnected=${webSocket != null} sent=$sent")
    }

    fun unsubscribe(planId: String) {
        activeSubscriptions.remove(planId)
        val cmd = UnsubscribeCommand(planId = planId)
        webSocket?.send(unsubscribeAdapter.toJson(cmd))
    }

    fun sendMessage(planId: String, destinationId: String, text: String) {
        val cmd = SendChatCommand(planId = planId, destinationId = destinationId, text = text)
        webSocket?.send(sendAdapter.toJson(cmd))
    }

    fun setActiveView(key: String) {
        activeViewKey = key
        clearUnread(key)
    }

    fun clearActiveView() {
        activeViewKey = null
    }

    fun clearUnread(key: String) {
        _unreadCounts.value = _unreadCounts.value.toMutableMap().apply { remove(key) }
    }

    private fun doConnect() {
        if (webSocket != null) return
        val jwt = tokenStore.getJwt() ?: return

        val request = Request.Builder()
            .url("$WS_BASE_URL?token=$jwt")
            .build()

        webSocket = wsClient.newWebSocket(request, object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                Log.d(TAG, "WebSocket connected")
                reconnectAttempt = 0
                // Re-subscribe to all active plans
                for (planId in activeSubscriptions) {
                    Log.d(TAG, "Re-subscribing to plan $planId")
                    val cmd = SubscribeCommand(planId = planId)
                    webSocket.send(subscribeAdapter.toJson(cmd))
                }
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                handleMessage(text)
            }

            override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                webSocket.close(1000, null)
            }

            override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                Log.d(TAG, "WebSocket closed: $code $reason")
                this@ChatRepository.webSocket = null
                scheduleReconnect()
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                Log.e(TAG, "WebSocket failure", t)
                this@ChatRepository.webSocket = null
                scheduleReconnect()
            }
        })
    }

    private fun scheduleReconnect() {
        if (!shouldConnect) return
        val delayMs = minOf(1000L * (1 shl reconnectAttempt.coerceAtMost(5)), MAX_RECONNECT_DELAY_MS)
        reconnectAttempt++
        Log.d(TAG, "Reconnecting in ${delayMs}ms (attempt $reconnectAttempt)")
        scope.launch {
            delay(delayMs)
            if (shouldConnect && webSocket == null) {
                doConnect()
            }
        }
    }

    private fun handleMessage(text: String) {
        try {
            Log.d(TAG, "WS message received: ${text.take(200)}")
            // Peek at type field to dispatch
            val json = moshi.adapter(Map::class.java).fromJson(text) ?: return
            when (json["type"]) {
                "chat_message" -> {
                    val event = messageAdapter.fromJson(text) ?: return
                    val key = "${event.planId}:${event.service}:${event.destinationId}"

                    // Append to messages (ring buffer capped at MAX_MESSAGES_PER_DEST)
                    _messages.value = _messages.value.toMutableMap().apply {
                        val existing = this[key] ?: emptyList()
                        val updated = if (existing.size >= MAX_MESSAGES_PER_DEST) {
                            existing.drop(1) + event.message
                        } else {
                            existing + event.message
                        }
                        this[key] = updated
                    }

                    // Increment unread if not actively viewing
                    if (activeViewKey != key) {
                        _unreadCounts.value = _unreadCounts.value.toMutableMap().apply {
                            this[key] = (this[key] ?: 0) + 1
                        }
                    }

                    // Emit for notifications
                    _latestMessage.tryEmit(event)
                }
                "chat_status" -> {
                    val event = statusAdapter.fromJson(text) ?: return
                    if (event.service != null && event.destinationId != null) {
                        val key = "${event.planId}:${event.service}:${event.destinationId}"
                        _connectionStatus.value = _connectionStatus.value.toMutableMap().apply {
                            this[key] = ChatConnectionStatus(
                                connected = event.connected ?: false,
                                error = event.error,
                            )
                        }
                    }
                }
                "error" -> {
                    Log.e(TAG, "Server error: ${json["error"]}")
                }
            }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to parse WebSocket message", e)
        }
    }
}
