package com.rosetta.messenger.network

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,
    PAUSED,
    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>()
    /** Последние параметры скачивания — нужны для resume */
    private val requests = mutableMapOf<String, DownloadRequest>()
    /** Флаг, что cancel произошёл именно как user pause */
    private val pauseRequested = mutableSetOf<String>()
    /** Если пользователь очень быстро тапнул pause→resume, запускаем resume после фактической паузы */
    private val resumeAfterPause = mutableSetOf<String>()

    private data class DownloadRequest(
        val attachmentId: String,
        val downloadTag: String,
        val transportServer: String,
        val chachaKey: String,
        val privateKey: String,
        val accountPublicKey: String,
        val fileName: String,
        val savedFile: File
    )

    private fun encryptedPartFile(request: DownloadRequest): File {
        val parent = request.savedFile.parentFile ?: request.savedFile.absoluteFile.parentFile
        val safeId = request.attachmentId.take(32).replace(Regex("[^A-Za-z0-9._-]"), "_")
        return File(parent, ".dl_${safeId}.part")
    }

    // ─── 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.QUEUED ||
            state.status == FileDownloadStatus.DOWNLOADING ||
            state.status == FileDownloadStatus.DECRYPTING
    }

    fun isPaused(attachmentId: String): Boolean =
        _downloads.value[attachmentId]?.status == FileDownloadStatus.PAUSED

    fun stateOf(attachmentId: String): FileDownloadState? = _downloads.value[attachmentId]

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

    /**
     * Запускает скачивание файла. Если уже скачивается — игнорирует.
     * Скачивание продолжается даже если пользователь вышел из чата.
     */
    fun download(
        attachmentId: String,
        downloadTag: String,
        transportServer: String = "",
        chachaKey: String,
        privateKey: String,
        accountPublicKey: String,
        fileName: String,
        savedFile: File
    ) {
        val request = DownloadRequest(
            attachmentId = attachmentId,
            downloadTag = downloadTag,
            transportServer = transportServer.trim(),
            chachaKey = chachaKey,
            privateKey = privateKey,
            accountPublicKey = accountPublicKey.trim(),
            fileName = fileName,
            savedFile = savedFile
        )
        requests[attachmentId] = request
        startDownload(request)
    }

    fun pause(attachmentId: String) {
        val current = _downloads.value[attachmentId] ?: return
        if (
            current.status == FileDownloadStatus.DONE ||
            current.status == FileDownloadStatus.ERROR
        ) return

        pauseRequested.add(attachmentId)
        val pausedProgress = current.progress.coerceIn(0f, 0.98f)
        update(
            id = attachmentId,
            fileName = current.fileName,
            status = FileDownloadStatus.PAUSED,
            progress = pausedProgress,
            accountPublicKey = current.accountPublicKey,
            savedPath = current.savedPath
        )

        TransportManager.cancelDownload(attachmentId)
        jobs[attachmentId]?.cancel()
    }

    fun resume(attachmentId: String) {
        val request = requests[attachmentId] ?: return
        if (jobs[attachmentId]?.isActive == true) {
            resumeAfterPause.add(attachmentId)
            return
        }
        pauseRequested.remove(attachmentId)
        startDownload(request)
    }

    /**
     * Отменяет скачивание
     */
    fun cancel(attachmentId: String) {
        requests[attachmentId]?.let { req ->
            encryptedPartFile(req).delete()
        }
        pauseRequested.remove(attachmentId)
        resumeAfterPause.remove(attachmentId)
        requests.remove(attachmentId)
        TransportManager.cancelDownload(attachmentId)
        jobs[attachmentId]?.cancel()
        jobs.remove(attachmentId)
        _downloads.update { it - attachmentId }
    }

    private fun startDownload(request: DownloadRequest) {
        val attachmentId = request.attachmentId
        if (jobs[attachmentId]?.isActive == true) return

        pauseRequested.remove(attachmentId)

        val savedPath = request.savedFile.absolutePath
        val encryptedPart = encryptedPartFile(request)
        val resumeBase =
            (_downloads.value[attachmentId]
                ?.takeIf { it.status == FileDownloadStatus.PAUSED }
                ?.progress
                ?: 0f).coerceIn(0f, 0.8f)

        update(
            attachmentId,
            request.fileName,
            FileDownloadStatus.QUEUED,
            resumeBase,
            request.accountPublicKey,
            savedPath
        )

        jobs[attachmentId] = scope.launch {
            var progressJob: Job? = null
            try {
                update(
                    attachmentId,
                    request.fileName,
                    FileDownloadStatus.DOWNLOADING,
                    resumeBase,
                    request.accountPublicKey,
                    savedPath
                )

                // Запускаем polling прогресса из TransportManager.
                // Держим прогресс монотонным, чтобы он не дёргался вниз.
                progressJob = launch {
                    TransportManager.downloading.collect { list ->
                        val entry = list.find { it.id == attachmentId } ?: return@collect
                        val rawCdn = (entry.progress / 100f) * 0.8f
                        val current = _downloads.value[attachmentId]?.progress ?: 0f
                        val stable = maxOf(current, rawCdn).coerceIn(0f, 0.8f)
                        update(
                            attachmentId,
                            request.fileName,
                            FileDownloadStatus.DOWNLOADING,
                            stable,
                            request.accountPublicKey,
                            savedPath
                        )
                    }
                }

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

                if (success) {
                    update(
                        attachmentId,
                        request.fileName,
                        FileDownloadStatus.DONE,
                        1f,
                        request.accountPublicKey,
                        savedPath
                    )
                    encryptedPart.delete()
                    requests.remove(attachmentId)
                } else {
                    update(
                        attachmentId,
                        request.fileName,
                        FileDownloadStatus.ERROR,
                        0f,
                        request.accountPublicKey,
                        savedPath
                    )
                }
            } catch (e: CancellationException) {
                if (pauseRequested.remove(attachmentId)) {
                    val current = _downloads.value[attachmentId]
                    val pausedProgress = (current?.progress ?: resumeBase).coerceIn(0f, 0.98f)
                    update(
                        attachmentId,
                        request.fileName,
                        FileDownloadStatus.PAUSED,
                        pausedProgress,
                        request.accountPublicKey,
                        savedPath
                    )
                } else {
                    throw e
                }
            } catch (e: Exception) {
                e.printStackTrace()
                update(
                    attachmentId,
                    request.fileName,
                    FileDownloadStatus.ERROR,
                    0f,
                    request.accountPublicKey,
                    savedPath
                )
            } catch (_: OutOfMemoryError) {
                System.gc()
                update(
                    attachmentId,
                    request.fileName,
                    FileDownloadStatus.ERROR,
                    0f,
                    request.accountPublicKey,
                    savedPath
                )
            } finally {
                progressJob?.cancel()
                jobs.remove(attachmentId)

                if (resumeAfterPause.remove(attachmentId)) {
                    scope.launch { startDownload(request) }
                }

                // Автоочистка только терминальных состояний.
                val terminalStatus = _downloads.value[attachmentId]?.status
                if (
                    terminalStatus == FileDownloadStatus.DONE ||
                    terminalStatus == FileDownloadStatus.ERROR
                ) {
                    scope.launch {
                        delay(5000)
                        val current = _downloads.value[attachmentId]
                        if (
                            current?.status == FileDownloadStatus.DONE ||
                            current?.status == FileDownloadStatus.ERROR
                        ) {
                            _downloads.update { it - attachmentId }
                        }
                    }
                }
            }
        }
    }

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

    private suspend fun downloadGroupFile(
        attachmentId: String,
        downloadTag: String,
        transportServer: String,
        chachaKey: String,
        privateKey: String,
        fileName: String,
        savedFile: File,
        encryptedPartFile: File,
        accountPublicKey: String,
        savedPath: String
    ): Boolean {
        val resumeBytes = if (encryptedPartFile.exists()) encryptedPartFile.length() else 0L
        val encryptedFile =
            TransportManager.downloadFileRawResumable(
                id = attachmentId,
                tag = downloadTag,
                targetFile = encryptedPartFile,
                resumeFromBytes = resumeBytes,
                transportServer = transportServer
            )
        val encryptedContent = withContext(Dispatchers.IO) {
            encryptedFile.readText(Charsets.UTF_8)
        }
        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,
        transportServer: String,
        chachaKey: String,
        privateKey: String,
        fileName: String,
        savedFile: File,
        encryptedPartFile: File,
        accountPublicKey: String,
        savedPath: String
    ): Boolean {
        val resumeBytes = if (encryptedPartFile.exists()) encryptedPartFile.length() else 0L
        val tempFile =
            TransportManager.downloadFileRawResumable(
                id = attachmentId,
                tag = downloadTag,
                targetFile = encryptedPartFile,
                resumeFromBytes = resumeBytes,
                transportServer = transportServer
            )
        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 {
                encryptedPartFile.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 ->
            val previous = map[id]
            val normalizedProgress =
                when (status) {
                    FileDownloadStatus.DONE -> 1f
                    FileDownloadStatus.ERROR -> progress.coerceIn(0f, 1f)
                    else -> maxOf(progress, previous?.progress ?: 0f).coerceIn(0f, 1f)
                }
            map + (
                id to FileDownloadState(
                    attachmentId = id,
                    fileName = fileName,
                    status = status,
                    progress = normalizedProgress,
                    accountPublicKey = accountPublicKey,
                    savedPath = savedPath
                )
            )
        }
    }
}
