package com.seven.ikar.kotlin.feature.transfer

import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.seven.ikar.kotlin.app.IkarNativeApp
import com.seven.ikar.kotlin.core.local.TransferQueueStatus
import com.seven.ikar.kotlin.core.local.TransferQueueTypes
import com.seven.ikar.kotlin.core.model.StoryVisibility
import com.seven.ikar.kotlin.core.network.UploadFileSource
import kotlinx.coroutines.runBlocking
import java.io.File

class AttachmentUploadWorker(
    appContext: Context,
    params: WorkerParameters
) : CoroutineWorker(appContext, params) {
    override suspend fun doWork(): Result {
        val groupId = inputData.getString(KeyGroupId) ?: return Result.failure()
        val container = (applicationContext as IkarNativeApp).container
        val dao = container.localDatabase.transferQueueDao()
        val items = dao.getGroup(groupId)
        if (items.isEmpty()) {
            return Result.success()
        }

        val first = items.first()
        val isStoryUpload = first.type == TransferQueueTypes.StoryUpload
        val chatId = if (isStoryUpload) null else first.chatId ?: return Result.failure()
        val now = System.currentTimeMillis()
        val totalBytes = items.sumOf { it.fileSizeBytes.coerceAtLeast(0L) }
        val showForeground = totalBytes >= TransferForegroundNotification.LargeTransferThresholdBytes
        val notificationMessage = if (items.size == 1) first.fileName else "${items.size} файлов"
        val notificationTitle = if (isStoryUpload) "Загрузка истории" else "Загрузка вложений"
        if (showForeground) {
            setForeground(
                TransferForegroundNotification.createForegroundInfo(
                    context = applicationContext,
                    groupId = groupId,
                    title = notificationTitle,
                    message = notificationMessage,
                    progressPercent = null
                )
            )
        }
        dao.updateGroupStatus(groupId, TransferQueueStatus.Running, 5, null, now)
        var lastProgressPercent = 5
        var lastProgressAtMillis = now

        suspend fun publishProgress(bytesTransferred: Long, force: Boolean = false) {
            if (totalBytes <= 0L) {
                return
            }

            val progressPercent = ((bytesTransferred.coerceAtMost(totalBytes) * 100L) / totalBytes)
                .toInt()
                .coerceIn(5, 99)
            val currentTime = System.currentTimeMillis()
            val shouldPublish = force ||
                progressPercent != lastProgressPercent && currentTime - lastProgressAtMillis >= ProgressUpdateIntervalMillis ||
                progressPercent >= 99 && lastProgressPercent < 99
            if (!shouldPublish) {
                return
            }

            lastProgressPercent = progressPercent
            lastProgressAtMillis = currentTime
            dao.updateGroupStatus(groupId, TransferQueueStatus.Running, progressPercent, null, currentTime)
            if (showForeground) {
                setForeground(
                    TransferForegroundNotification.createForegroundInfo(
                        context = applicationContext,
                        groupId = groupId,
                        title = notificationTitle,
                        message = notificationMessage,
                        progressPercent = progressPercent
                    )
                )
            }
        }

        return runCatching {
            val uploadedBytesByItem = LongArray(items.size)
            val progressLock = Any()
            val files = items
                .sortedBy { it.sortOrder }
                .mapIndexed { index, item ->
                    UploadFileSource(
                        fileName = item.fileName,
                        contentType = item.contentType,
                        contentLength = item.fileSizeBytes,
                        onBytesWritten = { itemBytesWritten ->
                            val bytesTransferred = synchronized(progressLock) {
                                uploadedBytesByItem[index] = maxOf(uploadedBytesByItem[index], itemBytesWritten)
                                uploadedBytesByItem.sum()
                            }
                            runBlocking {
                                publishProgress(bytesTransferred)
                            }
                        }
                    ) {
                        File(item.localPath).inputStream()
                    }
                }

            container.executeAuthorized { session ->
                if (isStoryUpload) {
                    container.apiClient.createMediaStory(
                        accessToken = session.accessToken,
                        text = first.text,
                        visibility = first.storyVisibility.toStoryVisibility(),
                        hasProtectedContent = first.hasProtectedContent,
                        ttlSeconds = first.ttlSeconds ?: 86_400,
                        files = files
                    )
                } else if (files.size == 1) {
                    container.apiClient.uploadAttachment(
                        accessToken = session.accessToken,
                        chatId = chatId!!,
                        text = first.text,
                        replyToMessageId = first.replyToMessageId,
                        file = files.first()
                    )
                } else {
                    container.apiClient.uploadAttachmentAlbum(
                        accessToken = session.accessToken,
                        chatId = chatId!!,
                        text = first.text,
                        replyToMessageId = first.replyToMessageId,
                        files = files
                    )
                }
            }
        }.fold(
            onSuccess = {
                dao.updateGroupStatus(groupId, TransferQueueStatus.Completed, 100, null, System.currentTimeMillis())
                Result.success()
            },
            onFailure = { error ->
                dao.updateGroupStatus(
                    groupId = groupId,
                    status = TransferQueueStatus.Failed,
                    progressPercent = 0,
                    errorMessage = error.message,
                    updatedAtMillis = System.currentTimeMillis()
                )
                if (runAttemptCount < 3) Result.retry() else Result.failure()
            }
        )
    }

    companion object {
        const val KeyGroupId = "group_id"
        private const val ProgressUpdateIntervalMillis = 500L
    }
}

private fun String?.toStoryVisibility(): StoryVisibility =
    runCatching { StoryVisibility.valueOf(this ?: StoryVisibility.Everyone.name) }
        .getOrDefault(StoryVisibility.Everyone)
