package chats.data.repository import chats.data.remote.api.ChatApi import chats.data.remote.api.SendMessageRequest import chats.data.remote.dto.ChatDto import chats.data.remote.dto.MessageDto import chats.data.remote.dto.MediaItemDto import chats.data.remote.dto.ReactionDto import chats.domain.model.Chat import chats.domain.model.Message import chats.domain.model.MediaType import chats.domain.repository.ChatRepository import core.network.ServerConfig import chats.data.remote.signalr.ReadMessagesRequest import core.security.TokenManager import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.asRequestBody import javax.inject.Inject import kotlinx.coroutines.flow.map class ChatRepositoryImpl @Inject constructor( private val api: ChatApi, private val tokenManager: TokenManager, private val serverConfig: ServerConfig, private val messageDao: core.database.data.MessageDao, private val hubClient: chats.data.remote.signalr.ChatHubClient ) : ChatRepository { private val gson = com.google.gson.Gson() override suspend fun getChats(): List { val currentUserId = tokenManager.getUserId() ?: "" val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/") return api.getChats().map { it.toDomain(currentUserId, baseUrl) } } override fun getMessagesFlow(chatId: String): kotlinx.coroutines.flow.Flow> { val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/") return messageDao.getMessages(chatId).map { entities: List -> entities.map { it.toDomain(baseUrl, gson) } } } override suspend fun getMessages(chatId: String, cursor: String?, pivot: Long?, limit: Int?): List { val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/") val currentUserId = tokenManager.getUserId() ?: "" return try { android.util.Log.d("ChatRepo", "FETCH: chatId=$chatId, cursor=$cursor, limit=$limit") val messages = api.getMessages(chatId, cursor = cursor, limit = limit) if (messages.isNotEmpty()) { android.util.Log.d("ChatRepo", "Received ${messages.size} messages. TopSeq: ${messages.first().sequenceId}, BottomSeq: ${messages.last().sequenceId}") } // Мапим в доменные модели. По умолчанию считаем прочитанными, // так как unreadCount нам тут не критичен для истории. messages.map { msg -> msg.toDomain(currentUserId, baseUrl).copy(isRead = true) } } catch (e: Exception) { android.util.Log.e("ChatRepo", "Fetch messages failed", e) emptyList() } } override suspend fun sendMessage( chatId: String, content: String?, type: String, attachments: List?, replyToId: String? ): Message { val request = SendMessageRequest( content = content, type = type, attachments = attachments, replyToId = replyToId ) val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/") val userId = tokenManager.getUserId() ?: "" return api.sendMessage(chatId, request).toDomain(userId, baseUrl) } override suspend fun addReaction(messageId: String, emoji: String) { api.addReaction(messageId, emoji) } override suspend fun sendTypingStatus(chatId: String) { api.sendTypingStatus(chatId) } override suspend fun markMessagesAsRead(chatId: String, lastMessageId: String, lastReadSequenceId: Int) { try { android.util.Log.d("ChatRepoImpl", "markMessagesAsRead CALLED FOR $chatId. Caller stack: ${android.util.Log.getStackTraceString(Throwable())}") hubClient.readMessages(ReadMessagesRequest(chatId, lastMessageId, lastReadSequenceId)) } catch (e: Exception) { android.util.Log.e("ChatRepo", "Error marking messages as read", e) } // Обновляем локальную БД messageDao.markMessagesAsRead(chatId, lastReadSequenceId) } override suspend fun saveMessage(message: Message) { android.util.Log.d("ChatRepo", "DB cache disabled, skipping save: ${message.id}") } override suspend fun deleteLocalMessage(messageId: String) { messageDao.deleteMessage(messageId) } override suspend fun uploadMedia(file: java.io.File): String { val mimeType = when (file.extension.lowercase()) { "jpg", "jpeg" -> "image/jpeg" "png" -> "image/png" "webp" -> "image/webp" "mp4" -> "video/mp4" "mp3", "m4a", "wav" -> "audio/mpeg" else -> "application/octet-stream" } val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull()) val body = MultipartBody.Part.createFormData("file", file.name, requestFile) return api.uploadFile(body).url } override suspend fun getTrendingGifs(page: Int): List { return api.getTrendingGifs(page).data.data } override suspend fun searchGifs(query: String, page: Int): List { return api.searchGifs(query, page).data.data } override suspend fun getGifCategories(): List { return api.getGifCategories().data.categories } override suspend fun createPersonalChat(userId: String): Chat { val currentUserId = tokenManager.getUserId() ?: "" val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/") val request = chats.data.remote.api.CreatePersonalChatRequest(userId) return api.createPersonalChat(request).toDomain(currentUserId, baseUrl) } }