package com.rosetta.messenger.network

import android.content.Context
import com.rosetta.messenger.crypto.CryptoManager
import com.rosetta.messenger.crypto.MessageCrypto
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.io.File

data class FileDownloadState(
    val attachmentId: String,
    val fileName: String,
    val status: FileDownloadStatus,
    /** 0f..1f */
    val progress: Float = 0f,
    val accountPublicKey: String = "",
    val savedPath: String = ""
)

enum class FileDownloadStatus {
    QUEUED,
    DOWNLOADING,
    DECRYPTING,
    DONE,
    ERROR
}

object FileDownloadManager {

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)

    /** Все активные/завершённые скачивания */
    private val _downloads = MutableStateFlow<Map<String, FileDownloadState>>(emptyMap())
    val downloads: StateFlow<Map<String, FileDownloadState>> = _downloads.asStateFlow()

    /** Текущие Job'ы — чтобы не запускать повторно */
    private val jobs = mutableMapOf<String, Job>()

    // ─── helpers ───

    private fun isGroupStoredKey(value: String): Boolean = value.startsWith("group:")

    private fun decodeGroupPassword(storedKey: String, privateKey: String): String? {
        if (!isGroupStoredKey(storedKey)) return null
        val encoded = storedKey.removePrefix("group:")
        if (encoded.isBlank()) return null
        return CryptoManager.decryptWithPassword(encoded, privateKey)
    }

    private fun decodeBase64Payload(data: String): ByteArray? {
        val raw = data.trim()
        if (raw.isBlank()) return null
        val payload =
            if (raw.startsWith("data:") && raw.contains(",")) raw.substringAfter(",")
            else raw
        return try {
            android.util.Base64.decode(payload, android.util.Base64.DEFAULT)
        } catch (_: Exception) {
            null
        }
    }

    // ─── public API ───

    /**
     * Проверяет, идёт ли уже скачивание этого attachment
     */
    fun isDownloading(attachmentId: String): Boolean {
        val state = _downloads.value[attachmentId] ?: return false
        return state.status == FileDownloadStatus.DOWNLOADING || state.status == FileDownloadStatus.DECRYPTING
    }

    /**
     * Возвращает Flow<FileDownloadState?> для конкретного attachment
     */
    fun progressOf(attachmentId: String): Flow<FileDownloadState?> =
        _downloads.map { it[attachmentId] }.distinctUntilChanged()

    /**
     * Запускает скачивание файла. Если уже скачивается — игнорирует.
     * Скачивание продолжается даже если пользователь вышел из чата.
     */
    fun download(
        context: Context,
        attachmentId: String,
        downloadTag: String,
        chachaKey: String,
        privateKey: String,
        accountPublicKey: String,
        fileName: String,
        savedFile: File
    ) {
        // Уже в процессе?
        if (jobs[attachmentId]?.isActive == true) return
        val normalizedAccount = accountPublicKey.trim()
        val savedPath = savedFile.absolutePath

        update(attachmentId, fileName, FileDownloadStatus.QUEUED, 0f, normalizedAccount, savedPath)

        jobs[attachmentId] = scope.launch {
            try {
                update(
                    attachmentId,
                    fileName,
                    FileDownloadStatus.DOWNLOADING,
                    0f,
                    normalizedAccount,
                    savedPath
                )

                // Запускаем polling прогресса из TransportManager
                val progressJob = launch {
                    TransportManager.downloading.collect { list ->
                        val entry = list.find { it.id == attachmentId }
                        if (entry != null) {
                            // CDN progress: 0-100 → 0f..0.8f (80% круга — скачивание)
                            val p = (entry.progress / 100f) * 0.8f
                            update(
                                attachmentId,
                                fileName,
                                FileDownloadStatus.DOWNLOADING,
                                p,
                                normalizedAccount,
                                savedPath
                            )
                        }
                    }
                }

                val success = withContext(Dispatchers.IO) {
                    if (isGroupStoredKey(chachaKey)) {
                        downloadGroupFile(
                            attachmentId = attachmentId,
                            downloadTag = downloadTag,
                            chachaKey = chachaKey,
                            privateKey = privateKey,
                            fileName = fileName,
                            savedFile = savedFile,
                            accountPublicKey = normalizedAccount,
                            savedPath = savedPath
                        )
                    } else {
                        downloadDirectFile(
                            attachmentId = attachmentId,
                            downloadTag = downloadTag,
                            chachaKey = chachaKey,
                            privateKey = privateKey,
                            fileName = fileName,
                            savedFile = savedFile,
                            accountPublicKey = normalizedAccount,
                            savedPath = savedPath
                        )
                    }
                }

                progressJob.cancel()

                if (success) {
                    update(
                        attachmentId,
                        fileName,
                        FileDownloadStatus.DONE,
                        1f,
                        normalizedAccount,
                        savedPath
                    )
                } else {
                    update(
                        attachmentId,
                        fileName,
                        FileDownloadStatus.ERROR,
                        0f,
                        normalizedAccount,
                        savedPath
                    )
                }
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                e.printStackTrace()
                update(
                    attachmentId,
                    fileName,
                    FileDownloadStatus.ERROR,
                    0f,
                    normalizedAccount,
                    savedPath
                )
            } catch (_: OutOfMemoryError) {
                System.gc()
                update(
                    attachmentId,
                    fileName,
                    FileDownloadStatus.ERROR,
                    0f,
                    normalizedAccount,
                    savedPath
                )
            } finally {
                jobs.remove(attachmentId)
                // Автоочистка через 5 секунд после завершения
                scope.launch {
                    delay(5000)
                    _downloads.update { it - attachmentId }
                }
            }
        }
    }

    /**
     * Отменяет скачивание
     */
    fun cancel(attachmentId: String) {
        jobs[attachmentId]?.cancel()
        jobs.remove(attachmentId)
        _downloads.update { it - attachmentId }
    }

    // ─── internal download logic (moved from FileAttachment) ───

    private suspend fun downloadGroupFile(
        attachmentId: String,
        downloadTag: String,
        chachaKey: String,
        privateKey: String,
        fileName: String,
        savedFile: File,
        accountPublicKey: String,
        savedPath: String
    ): Boolean {
        val encryptedContent = TransportManager.downloadFile(attachmentId, downloadTag)
        update(
            attachmentId,
            fileName,
            FileDownloadStatus.DECRYPTING,
            0.82f,
            accountPublicKey,
            savedPath
        )

        val groupPassword = decodeGroupPassword(chachaKey, privateKey)
        if (groupPassword.isNullOrBlank()) return false

        val decrypted = CryptoManager.decryptWithPassword(encryptedContent, groupPassword)
        update(
            attachmentId,
            fileName,
            FileDownloadStatus.DECRYPTING,
            0.88f,
            accountPublicKey,
            savedPath
        )

        val bytes = decrypted?.let { decodeBase64Payload(it) } ?: return false
        update(
            attachmentId,
            fileName,
            FileDownloadStatus.DECRYPTING,
            0.93f,
            accountPublicKey,
            savedPath
        )

        withContext(Dispatchers.IO) {
            savedFile.parentFile?.mkdirs()
            savedFile.writeBytes(bytes)
        }
        update(
            attachmentId,
            fileName,
            FileDownloadStatus.DECRYPTING,
            0.98f,
            accountPublicKey,
            savedPath
        )
        return true
    }

    private suspend fun downloadDirectFile(
        attachmentId: String,
        downloadTag: String,
        chachaKey: String,
        privateKey: String,
        fileName: String,
        savedFile: File,
        accountPublicKey: String,
        savedPath: String
    ): Boolean {
        // Streaming: скачиваем во temp file
        val tempFile = TransportManager.downloadFileRaw(attachmentId, downloadTag)
        update(
            attachmentId,
            fileName,
            FileDownloadStatus.DECRYPTING,
            0.83f,
            accountPublicKey,
            savedPath
        )

        val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
        update(
            attachmentId,
            fileName,
            FileDownloadStatus.DECRYPTING,
            0.88f,
            accountPublicKey,
            savedPath
        )

        // Streaming decrypt: tempFile → AES → inflate → base64 → savedFile
        withContext(Dispatchers.IO) {
            try {
                MessageCrypto.decryptAttachmentFileStreaming(
                    tempFile,
                    decryptedKeyAndNonce,
                    savedFile
                )
            } finally {
                tempFile.delete()
            }
        }
        update(
            attachmentId,
            fileName,
            FileDownloadStatus.DECRYPTING,
            0.98f,
            accountPublicKey,
            savedPath
        )
        return true
    }

    private fun update(
        id: String,
        fileName: String,
        status: FileDownloadStatus,
        progress: Float,
        accountPublicKey: String,
        savedPath: String
    ) {
        _downloads.update { map ->
            map + (
                id to FileDownloadState(
                    attachmentId = id,
                    fileName = fileName,
                    status = status,
                    progress = progress,
                    accountPublicKey = accountPublicKey,
                    savedPath = savedPath
                )
            )
        }
    }
}
