package chats.data.workers import android.content.Context import android.util.Log import androidx.hilt.work.HiltWorker import androidx.work.* import chats.data.local.dao.ChatDao import chats.data.local.dao.MessageDao import chats.domain.model.MessageStatus import core.security.TokenManager import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.IOException import java.net.SocketTimeoutException import java.util.concurrent.TimeUnit /** * Worker для периодической синхронизации чатов и сообщений. * Запускается при наличии сети для получения новых сообщений с сервера. */ @HiltWorker class ChatSyncWorker @AssistedInject constructor( @Assisted context: Context, @Assisted params: WorkerParameters, private val chatDao: ChatDao, private val messageDao: MessageDao, private val chatApi: chats.data.remote.api.ChatApi ) : CoroutineWorker(context, params) { companion object { const val WORK_TAG = "chat_sync_worker" const val KEY_CHAT_ID = "chat_id" const val KEY_CURSOR = "cursor" /** * Создает периодический запрос на синхронизацию всех чатов */ fun createPeriodicWorkRequest(): PeriodicWorkRequest { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() return PeriodicWorkRequestBuilder( repeatInterval = 15, repeatIntervalTimeUnit = TimeUnit.MINUTES ) .setConstraints(constraints) .addTag(WORK_TAG) .build() } /** * Создает разовый запрос на синхронизацию конкретного чата */ fun createOneTimeWorkRequest(chatId: String? = null, cursor: String? = null): OneTimeWorkRequest { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() val inputData = workDataOf( KEY_CHAT_ID to chatId, KEY_CURSOR to cursor ) return OneTimeWorkRequestBuilder() .setConstraints(constraints) .setInputData(inputData) .addTag(WORK_TAG) .build() } } override suspend fun doWork(): Result = withContext(Dispatchers.IO) { Log.d("ChatSyncWorker", "Starting chat sync") try { // 1. Синхронизируем список чатов syncChats() // 2. Если указан chatId - синхронизируем сообщения val chatId = inputData.getString(KEY_CHAT_ID) if (chatId != null) { syncMessagesForChat(chatId) } Log.i("ChatSyncWorker", "Sync completed successfully") Result.success() } catch (e: IOException) { Log.w("ChatSyncWorker", "Network error during sync: ${e.message}") Result.retry() } catch (e: SocketTimeoutException) { Log.w("ChatSyncWorker", "Timeout during sync: ${e.message}") Result.retry() } catch (e: Exception) { Log.e("ChatSyncWorker", "Error during sync", e) Result.failure() } } private suspend fun syncChats() { val remoteChats = chatApi.getChats() val localChats = mutableListOf() remoteChats.forEach { dto -> val existingChat = chatDao.getChatByRemoteId(dto.id) val chatEntity = chats.data.local.database.ChatEntity( localId = existingChat?.localId ?: java.util.UUID.randomUUID().toString(), remoteId = dto.id, type = dto.type, name = dto.name ?: "", avatar = dto.avatar, unreadCount = dto.unreadCount, lastMessageText = dto.messages.firstOrNull()?.content, lastMessageTimestamp = dto.messages.firstOrNull()?.createdAt ) localChats.add(chatEntity) } chatDao.insertChats(localChats) Log.d("ChatSyncWorker", "Synced ${localChats.size} chats") } private suspend fun syncMessagesForChat(chatId: String) { val lastMessage = messageDao.getMessagesByStatus( chatId = chatId, statuses = listOf(MessageStatus.SENT, MessageStatus.DELIVERED, MessageStatus.READ) ).maxByOrNull { it.sequenceId } val cursor = lastMessage?.serverId val messages = chatApi.getMessages(chatId, cursor = cursor, limit = 50) if (messages.isNotEmpty()) { saveMessagesToLocal(messages, chatId) Log.d("ChatSyncWorker", "Synced ${messages.size} messages for chat $chatId") } } private suspend fun saveMessagesToLocal( messages: List, chatId: String ) { val currentUserId = tokenManager.getUserId() ?: "" val gson = com.google.gson.Gson() messages.forEach { dto -> val existing = messageDao.getMessageByServerId(dto.id) if (existing == null) { val entity = chats.data.local.database.MessageEntity( localId = java.util.UUID.randomUUID().toString(), serverId = dto.id, idempotencyKey = dto.id, chatId = chatId, senderId = dto.senderId ?: dto.sender?.id ?: currentUserId, senderName = dto.sender?.displayName ?: dto.sender?.username ?: "", senderAvatar = dto.sender?.avatarUrl, content = dto.content, sequenceId = dto.sequenceId?.toLong() ?: 0L, createdAt = dto.createdAt ?: java.time.ZonedDateTime.now().toString(), mediaType = dto.type ?: "TEXT", mediaJson = gson.toJson(dto.media.map { chats.domain.model.Media( id = it.id, type = it.type, url = it.url, filename = it.filename, size = it.size, duration = it.duration ) }), reactionsJson = gson.toJson( dto.reactions?.associate { it.emoji to it.count } ?: emptyMap() ), status = if (dto.senderId == currentUserId) MessageStatus.SENT else MessageStatus.DELIVERED, createdAtMillis = System.currentTimeMillis(), updatedAtMillis = System.currentTimeMillis() ) messageDao.insertMessage(entity) } } } }