package top.yeij.cyrene.viewmodel

import android.app.Application
import android.net.Uri
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.toRequestBody
import top.yeij.cyrene.data.local.PreferencesDataStore
import top.yeij.cyrene.data.remote.ApiService
import top.yeij.cyrene.data.remote.dto.WSAttachment
import top.yeij.cyrene.domain.model.Conversation
import top.yeij.cyrene.domain.model.Message
import top.yeij.cyrene.domain.repository.ChatRepository
import top.yeij.cyrene.util.RecordState
import top.yeij.cyrene.util.RuntimeLog
import top.yeij.cyrene.util.VoiceRecorder

private fun List<Message>.deduplicate(): List<Message> {
    if (isEmpty()) return this
    val seen = mutableSetOf<String>()
    return filter { seen.add(it.id) }
}

class ChatViewModel(
    application: Application,
    private val chatRepository: ChatRepository,
    private val voiceRecorder: VoiceRecorder,
    private val preferencesDataStore: PreferencesDataStore,
    private val apiService: ApiService,
) : AndroidViewModel(application) {

    companion object {
        private var instanceCounter = 0
    }
    private val instanceId = ++instanceCounter

    val isConnected: StateFlow<Boolean> = chatRepository.connectionState
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)

    val connectionError: StateFlow<String?> = chatRepository.connectionError

    val conversations: StateFlow<List<Conversation>> = chatRepository.getConversations()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    private val _currentMessages = MutableStateFlow<List<Message>>(emptyList())
    val currentMessages: StateFlow<List<Message>> = _currentMessages.asStateFlow()

    private val _inputText = MutableStateFlow("")
    val inputText: StateFlow<String> = _inputText.asStateFlow()

    private val _isSending = MutableStateFlow(false)
    val isStreaming: StateFlow<Boolean> = kotlinx.coroutines.flow.combine(
        _isSending,
        chatRepository.isAssistantStreaming,
    ) { sending, assistant -> sending || assistant }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)

    private val _isRefreshing = MutableStateFlow(false)
    val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()

    // Voice recording state
    val voiceRecordState: StateFlow<RecordState> = voiceRecorder.state
    val voiceRecordDurationMs: StateFlow<Long> = voiceRecorder.durationMs

    // Animation ordering for message bubbles
    private var animCounter = 0
    private val _messageAnimIndex = MutableStateFlow<Map<String, Int>>(emptyMap())
    val messageAnimIndex: StateFlow<Map<String, Int>> = _messageAnimIndex.asStateFlow()

    private var currentSessionId: String? = null
    private var dbObserverJob: Job? = null
    private var sendTimeoutJob: Job? = null

    init {
        RuntimeLog.general("app", "ChatViewModel instance #$instanceId created")
        viewModelScope.launch {
            try {
                RuntimeLog.general("app", "ChatViewModel #$instanceId — initializing session...")
                val sessionId = chatRepository.initializeSession()
                currentSessionId = sessionId
                chatRepository.currentSessionId = sessionId
                RuntimeLog.general("app", "Session initialized: $sessionId")
                chatRepository.ensureConnected()
            } catch (e: Exception) {
                RuntimeLog.general("app", "initializeSession failed: ${e.message}")
            }
            // Always try to load from DB, even if initializeSession failed.
            // After process death the persisted session ID gives us the history.
            val sid = currentSessionId
                ?: preferencesDataStore.currentSessionId.firstOrNull()
            if (sid != null) {
                currentSessionId = sid
                chatRepository.currentSessionId = sid
                RuntimeLog.general("app", "Loading messages from DB for session=$sid")
                loadMessagesFromDb(sid)
            } else {
                RuntimeLog.general("app", "No session ID available — cannot load messages")
            }
        }

        // Observe incoming live messages — insert at correct descending position
        viewModelScope.launch {
            chatRepository.observeMessages().collect { message ->
                try {
                    _currentMessages.update { list ->
                        val updated = list.toMutableList()
                        val existingIdx = updated.indexOfLast { it.id == message.id }
                        if (existingIdx >= 0) {
                            updated[existingIdx] = message
                        } else {
                            // Insert at correct position for descending timestamp (newest first)
                            val insertAt = updated.indexOfFirst { it.timestamp <= message.timestamp }
                            if (insertAt >= 0) updated.add(insertAt, message) else updated.add(message)
                            val idx = _messageAnimIndex.value.toMutableMap()
                            idx[message.id] = animCounter++
                            _messageAnimIndex.value = idx
                        }
                        updated.deduplicate()
                    }
                    // Any non-user response means the server acknowledged our message
                    if (_isSending.value && message.role != "user") {
                        _isSending.value = false
                        sendTimeoutJob?.cancel()
                    }
                } catch (e: Exception) {
                    Log.e("ChatViewModel", "Error processing message: ${e.message}", e)
                }
            }
        }

        // Observe message clear events
        viewModelScope.launch {
            chatRepository.messageClearEvents.collect {
                _currentMessages.value = emptyList()
                _messageAnimIndex.value = emptyMap()
                animCounter = 0
            }
        }
        // Observe message removals (e.g. wrapping stream_end deduped by review items)
        viewModelScope.launch {
            chatRepository.messageRemovals.collect { msgId ->
                _currentMessages.update { list -> list.filter { it.id != msgId } }
                val idx = _messageAnimIndex.value.toMutableMap()
                idx.remove(msgId)
                _messageAnimIndex.value = idx
            }
        }
        // Reset user-side sending state when server starts responding
        viewModelScope.launch {
            chatRepository.isAssistantStreaming.collect { streaming ->
                if (streaming) {
                    _isSending.value = false
                    sendTimeoutJob?.cancel()
                }
            }
        }

    }

    // --- Voice recording (WeChat-style gesture) ---

    fun startRecord() {
        voiceRecorder.start()
    }

    fun lockRecord() {
        voiceRecorder.lock()
    }

    fun finishRecord() {
        val file = voiceRecorder.stop() ?: return
        val base64 = voiceRecorder.getBase64()
        voiceRecorder.deleteFile()
        if (base64.isNullOrBlank()) return

        viewModelScope.launch {
            chatRepository.sendVoiceInput(base64, "voice_msg")
            RuntimeLog.chat("voice", "Voice message sent, duration=${file.length()}")
        }
    }

    fun cancelRecord() {
        voiceRecorder.cancel()
    }

    // --- Image attachments ---

    private val _selectedImageUris = MutableStateFlow<List<Uri>>(emptyList())
    val selectedImageUris: StateFlow<List<Uri>> = _selectedImageUris.asStateFlow()

    fun addImages(uris: List<Uri>) {
        _selectedImageUris.update { it + uris }
    }

    fun removeImage(index: Int) {
        _selectedImageUris.update { list ->
            list.filterIndexed { i, _ -> i != index }
        }
    }

    fun clearImages() {
        _selectedImageUris.value = emptyList()
    }

    private data class UploadResult(
        val attachment: WSAttachment,
        val thumbnailUrl: String,
    )

    private suspend fun uploadAndBuildAttachment(uri: Uri): UploadResult? {
        return withContext(Dispatchers.IO) {
            try {
                val cr = getApplication<Application>().contentResolver
                val mimeType = cr.getType(uri) ?: "image/jpeg"
                val filename = uri.lastPathSegment ?: "image"
                val bytes = cr.openInputStream(uri)?.use { it.readBytes() } ?: return@withContext null
                if (bytes.isEmpty()) return@withContext null

                // Upload to server, get file_id
                val requestBody = bytes.toRequestBody(mimeType.toMediaTypeOrNull())
                val part = MultipartBody.Part.createFormData("file", filename, requestBody)
                val response = apiService.uploadFile(part)
                if (!response.isSuccessful) {
                    Log.e("ChatViewModel", "Upload failed: ${response.code()} ${response.message()}")
                    return@withContext null
                }
                val fileId = response.body()?.id ?: return@withContext null

                // Construct thumbnail URL
                val baseUrl = preferencesDataStore.baseUrl.firstOrNull()
                    ?.trimEnd('/') ?: "http://10.0.2.2:8080"
                val thumbnailUrl = "$baseUrl/api/v1/files/$fileId/thumbnail"

                val attachment = WSAttachment(
                    type = "image",
                    fileId = fileId,
                    thumbnailUrl = thumbnailUrl,
                    filename = filename,
                    size = bytes.size.toLong(),
                )
                UploadResult(attachment, thumbnailUrl)
            } catch (e: Exception) {
                Log.e("ChatViewModel", "Failed to upload image: ${e.message}", e)
                null
            }
        }
    }

    // Override sendMessage to support image attachments
    fun sendMessage() {
        val text = _inputText.value.trim()
        val uris = _selectedImageUris.value
        if (text.isEmpty() && uris.isEmpty()) return
        val sid = currentSessionId
        if (sid == null) {
            RuntimeLog.chat("send", "Cannot send — no current session")
            return
        }

        _inputText.value = ""
        _isSending.value = true

        sendTimeoutJob?.cancel()
        sendTimeoutJob = viewModelScope.launch {
            delay(15_000L)
            if (_isSending.value) {
                Log.w("ChatViewModel", "Send timeout — no response in 15s, resetting")
                _isSending.value = false
            }
        }

        val localUriStrings = uris.map { it.toString() }

        viewModelScope.launch {
            val results = uris.mapNotNull { uploadAndBuildAttachment(it) }
            clearImages()
            val attachments = results.map { it.attachment }
            val thumbnailUrls = results.map { it.thumbnailUrl }
            try {
                chatRepository.sendMessage(
                    text, sid,
                    attachments = attachments.ifEmpty { null },
                    localImageUris = thumbnailUrls.ifEmpty { localUriStrings },
                )
            } catch (e: Exception) {
                Log.e("ChatViewModel", "sendMessage failed: ${e.message}", e)
                _isSending.value = false
                sendTimeoutJob?.cancel()
            }
        }
    }

    private fun loadMessagesFromDb(sessionId: String) {
        dbObserverJob?.cancel()
        dbObserverJob = viewModelScope.launch {
            try {
                chatRepository.getMessages(sessionId).collect { messages ->
                    _currentMessages.update { current ->
                        val live = current.associateBy { it.id }
                        val db = messages.associateBy { it.id }
                        (db + live).values
                            .sortedByDescending { it.timestamp }
                            .deduplicate()
                    }
                    val idx = _messageAnimIndex.value.toMutableMap()
                    messages.forEach { m ->
                        if (m.id !in idx) idx[m.id] = animCounter++
                    }
                    _messageAnimIndex.value = idx
                }
            } catch (e: Exception) {
                Log.e("ChatViewModel", "Error loading messages: ${e.message}", e)
                RuntimeLog.general("app", "loadMessagesFromDb failed: ${e.message}")
            }
        }
    }

    fun onInputChanged(text: String) {
        _inputText.value = text
    }

    fun switchSession(sessionId: String) {
        currentSessionId = sessionId
        chatRepository.currentSessionId = sessionId
        _currentMessages.value = emptyList()
        _messageAnimIndex.value = emptyMap()
        animCounter = 0
        viewModelScope.launch {
            chatRepository.connectWebSocket(sessionId)
            chatRepository.loadMessagesFromServer(sessionId)
            loadMessagesFromDb(sessionId)
        }
    }

    fun refreshMessages() {
        val sid = currentSessionId ?: return
        viewModelScope.launch {
            _isRefreshing.value = true
            try {
                // Clear all messages and let the DB observer rebuild from scratch.
                // This avoids duplicates that occur when local-UUID messages survive
                // the merge alongside server-ID versions loaded from HTTP.
                _currentMessages.value = emptyList()
                if (!isConnected.value) {
                    chatRepository.ensureConnected()
                }
                chatRepository.loadMessagesFromServer(sid)
            } catch (_: Exception) { }
            _isRefreshing.value = false
        }
    }

    fun deleteConversation(id: String) {
        viewModelScope.launch {
            chatRepository.deleteConversation(id)
        }
    }

    fun clearLocalMessages() {
        viewModelScope.launch {
            chatRepository.clearLocalMessages()
            _currentMessages.value = emptyList()
            _messageAnimIndex.value = emptyMap()
            animCounter = 0
        }
    }
}
