package com.rosetta.messenger.update

import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import androidx.core.content.FileProvider
import com.rosetta.messenger.BuildConfig
import com.rosetta.messenger.network.PacketRequestUpdate
import com.rosetta.messenger.network.ProtocolRuntimeAccess
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.io.File
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.TimeUnit

/**
 * Менеджер обновлений приложения через SDU (Software Distribution Unit).
 *
 * Аналог UpdateProvider из desktop-приложения.
 * Поток:
 * 1. После аутентификации отправляем PacketRequestUpdate (0x0A) для получения URL SDU сервера
 * 2. Запрашиваем /updates/get?kernel={version}&platform=android&arch={arch} на SDU
 * 3. Если есть обновление — показываем пользователю
 * 4. Скачиваем APK и открываем установщик
 */
object UpdateManager {
    private const val TAG = "UpdateManager"
    private const val PREFS_NAME = "rosetta_update_state"
    private const val KEY_DOWNLOAD_ID = "download_id"
    private const val KEY_APK_PATH = "apk_path"
    private const val KEY_APK_VERSION = "apk_version"

    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    @Volatile
    private var appContext: Context? = null

    // ═══ Debug log buffer ═══
    private val _debugLogs = MutableStateFlow<List<String>>(emptyList())
    val debugLogs: StateFlow<List<String>> = _debugLogs.asStateFlow()
    private val timeFmt = SimpleDateFormat("HH:mm:ss.SSS", Locale.US)

    private fun sduLog(msg: String) {
        Log.i(TAG, msg)
        val ts = timeFmt.format(Date())
        val line = "[$ts] $msg"
        _debugLogs.value = (_debugLogs.value + line).takeLast(200)
    }

    // SDU server URL, получаем от основного сервера через PacketRequestUpdate
    @Volatile
    private var sduServerUrl: String? = null

    // Текущая версия приложения
    val appVersion: String = BuildConfig.VERSION_NAME

    // Состояния обновления
    private val _updateState = MutableStateFlow<UpdateState>(UpdateState.Idle)
    val updateState: StateFlow<UpdateState> = _updateState.asStateFlow()

    private val _downloadProgress = MutableStateFlow(0)
    val downloadProgress: StateFlow<Int> = _downloadProgress.asStateFlow()

    // Информация о доступном обновлении
    @Volatile
    private var latestUpdateInfo: UpdateInfo? = null

    private var downloadMonitorJob: Job? = null
    @Volatile
    private var activeDownloadId: Long? = null
    @Volatile
    private var activeApkPath: String? = null
    @Volatile
    private var activeApkVersion: String? = null

    private val httpClient = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .build()

    /**
     * Инициализация: регистрируем обработчик пакета 0x0A и восстанавливаем состояние скачивания.
     */
    fun init(context: Context? = null) {
        context?.applicationContext?.let { appContext = it }
        sduLog("init() called, appVersion=$appVersion")
        appContext?.let { restorePersistedState(it) }

        sduLog("Registering waitPacket(0x0A) listener...")
        ProtocolRuntimeAccess.get().waitPacket(0x0A) { packet ->
            sduLog("Received packet 0x0A, type=${packet::class.simpleName}")
            if (packet is PacketRequestUpdate) {
                val server = packet.updateServer
                sduLog("PacketRequestUpdate.updateServer='$server'")
                if (server.isNotEmpty()) {
                    sduServerUrl = server
                    sduLog("SDU server set: $server")
                    scope.launch {
                        sduLog("Auto-checking for updates...")
                        checkForUpdates()
                    }
                } else {
                    sduLog("WARNING: updateServer is empty!")
                }
            } else {
                sduLog("WARNING: packet is not PacketRequestUpdate, got ${packet::class.simpleName}")
            }
        }
        sduLog("init() complete")
    }

    /**
     * Запросить URL SDU сервера у основного сервера
     */
    fun requestSduServer() {
        sduLog("requestSduServer() — sending PacketRequestUpdate with empty updateServer")
        val packet = PacketRequestUpdate().apply {
            updateServer = ""
        }
        ProtocolRuntimeAccess.get().sendPacket(packet)
        sduLog("PacketRequestUpdate sent")
    }

    /**
     * Проверить наличие обновлений на SDU сервере
     */
    suspend fun checkForUpdates(): UpdateInfo? = withContext(Dispatchers.IO) {
        sduLog("checkForUpdates() called")
        val sdu = sduServerUrl
        if (sdu == null) {
            sduLog("ERROR: sduServerUrl is null! Requesting again...")
            requestSduServer()
            return@withContext null
        }

        if (activeDownloadId != null) {
            sduLog("checkForUpdates skipped: download is already in progress")
            appContext?.let { startDownloadMonitor(it) }
            return@withContext latestUpdateInfo
        }
        val readyPath = activeApkPath
        if (_updateState.value is UpdateState.ReadyToInstall &&
            !readyPath.isNullOrBlank() &&
            File(readyPath).exists()
        ) {
            sduLog("checkForUpdates skipped: downloaded APK is ready to install")
            return@withContext latestUpdateInfo
        }

        _updateState.value = UpdateState.Checking
        sduLog("State -> Checking")

        try {
            val arch = Build.SUPPORTED_ABIS.firstOrNull() ?: "arm64-v8a"
            val url = "$sdu/updates/all?app=$appVersion&kernel=$appVersion&platform=android&arch=$arch"
            sduLog("HTTP GET: $url")

            val request = Request.Builder().url(url).get().build()
            val response = httpClient.newCall(request).execute()

            sduLog("HTTP response: ${response.code} ${response.message}")

            if (!response.isSuccessful) {
                sduLog("ERROR: SDU returned ${response.code}")
                _updateState.value = UpdateState.Error("Server returned ${response.code}")
                return@withContext null
            }

            val body = response.body?.string() ?: run {
                sduLog("ERROR: Empty response body")
                _updateState.value = UpdateState.Error("Empty response")
                return@withContext null
            }

            sduLog("Response body: $body")
            val info = parseUpdateResponse(body)
            latestUpdateInfo = info
            sduLog("Parsed: version=${info.version}, servicePackUrl=${info.servicePackUrl}, kernelUpdateRequired=${info.kernelUpdateRequired}")

            when {
                info.servicePackUrl != null -> {
                    sduLog("State -> UpdateAvailable(${info.version})")
                    _updateState.value = UpdateState.UpdateAvailable(info.version)
                }
                else -> {
                    sduLog("State -> UpToDate")
                    _updateState.value = UpdateState.UpToDate
                }
            }

            return@withContext info
        } catch (e: Exception) {
            sduLog("EXCEPTION: ${e::class.simpleName}: ${e.message}")
            _updateState.value = UpdateState.Error(e.message ?: "Unknown error")
            return@withContext null
        }
    }

    /**
     * Скачать обновление или открыть установщик, если APK уже скачан.
     *
     * Используем системный DownloadManager, чтобы загрузка продолжалась,
     * даже если пользователь свернул или закрыл приложение.
     */
    fun downloadAndInstall(context: Context) {
        val appCtx = context.applicationContext
        appContext = appCtx
        restorePersistedState(appCtx)

        val currentState = _updateState.value
        val readyPath = activeApkPath
        if (currentState is UpdateState.ReadyToInstall && !readyPath.isNullOrBlank()) {
            val readyFile = File(readyPath)
            if (readyFile.exists()) {
                installApk(appCtx, readyFile)
                return
            }
            activeApkPath = null
            activeApkVersion = null
            persistState(appCtx)
        }

        if (activeDownloadId != null) {
            startDownloadMonitor(appCtx)
            return
        }

        val info = latestUpdateInfo ?: return
        val sdu = sduServerUrl ?: return
        val url = info.servicePackUrl ?: return
        val fullUrl = if (url.startsWith("http")) url else "$sdu$url"
        val apkFile = buildApkFile(appCtx, info.version)
        if (apkFile.exists()) {
            apkFile.delete()
        }

        try {
            val request =
                DownloadManager.Request(Uri.parse(fullUrl))
                    .setTitle("Rosetta update ${info.version}")
                    .setDescription("Downloading update")
                    .setMimeType("application/vnd.android.package-archive")
                    .setAllowedOverMetered(true)
                    .setAllowedOverRoaming(true)
                    .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
            if (appCtx.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) != null) {
                request.setDestinationInExternalFilesDir(
                    appCtx,
                    Environment.DIRECTORY_DOWNLOADS,
                    apkFile.name
                )
            } else {
                request.setDestinationUri(Uri.fromFile(apkFile))
            }

            val downloadManager =
                appCtx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
            val downloadId = downloadManager.enqueue(request)
            activeDownloadId = downloadId
            activeApkPath = apkFile.absolutePath
            activeApkVersion = info.version
            persistState(appCtx)

            _downloadProgress.value = 0
            _updateState.value = UpdateState.Downloading(0)
            sduLog("DownloadManager enqueue id=$downloadId, file=${apkFile.absolutePath}")
            startDownloadMonitor(appCtx)
        } catch (e: Exception) {
            Log.e(TAG, "Failed to enqueue update download", e)
            sduLog("EXCEPTION enqueue: ${e::class.simpleName}: ${e.message}")
            _updateState.value = UpdateState.Error("Failed to start download: ${e.message}")
        }
    }

    /**
     * Открыть системный установщик APK
     */
    private fun installApk(context: Context, apkFile: File) {
        try {
            val uri: Uri = FileProvider.getUriForFile(
                context,
                "${context.packageName}.provider",
                apkFile
            )

            val intent = Intent(Intent.ACTION_VIEW).apply {
                setDataAndType(uri, "application/vnd.android.package-archive")
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
            }

            context.startActivity(intent)
            // Сбрасываем состояние — установщик запущен, баннер больше не нужен
            activeApkPath = null
            activeApkVersion = null
            persistState(context)
            _updateState.value = UpdateState.Idle
            _downloadProgress.value = 0
        } catch (e: Exception) {
            Log.e(TAG, "Failed to launch installer", e)
            _updateState.value = UpdateState.Error("Failed to open installer: ${e.message}")
        }
    }

    /**
     * Отменить текущее скачивание
     */
    fun cancelDownload() {
        val ctx = appContext
        val id = activeDownloadId
        if (ctx != null && id != null) {
            runCatching {
                val dm = ctx.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
                dm.remove(id)
            }
        }

        downloadMonitorJob?.cancel()
        downloadMonitorJob = null
        activeDownloadId = null
        activeApkPath?.let { path ->
            runCatching {
                val file = File(path)
                if (file.exists()) file.delete()
            }
        }
        activeApkPath = null
        activeApkVersion = null
        if (ctx != null) {
            persistState(ctx)
        }

        val availableVersion = resolveAvailableUpdateVersion()
        _updateState.value = if (availableVersion != null) {
            UpdateState.UpdateAvailable(availableVersion)
        } else {
            UpdateState.Idle
        }
        _downloadProgress.value = 0
    }

    /**
     * Сбросить состояние
     */
    fun reset() {
        cancelDownload()
        _updateState.value = UpdateState.Idle
        _downloadProgress.value = 0
        latestUpdateInfo = null
    }

    private fun prefs(context: Context) =
        context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

    private fun persistState(context: Context) {
        prefs(context)
            .edit()
            .putLong(KEY_DOWNLOAD_ID, activeDownloadId ?: -1L)
            .putString(KEY_APK_PATH, activeApkPath)
            .putString(KEY_APK_VERSION, activeApkVersion)
            .apply()
    }

    private fun setUpdateAvailableOrIdleState() {
        val version = resolveAvailableUpdateVersion()
        _updateState.value = if (!version.isNullOrBlank()) {
            UpdateState.UpdateAvailable(version)
        } else {
            UpdateState.Idle
        }
    }

    private fun resolveAvailableUpdateVersion(): String? {
        val latestVersion =
            latestUpdateInfo
                ?.takeIf {
                    !it.servicePackUrl.isNullOrBlank() &&
                        it.version.isNotBlank() &&
                        compareVersions(it.version, appVersion) > 0
                }
                ?.version
        if (!latestVersion.isNullOrBlank()) {
            return latestVersion
        }

        val persistedVersion = activeApkVersion
        return if (!persistedVersion.isNullOrBlank() &&
            compareVersions(persistedVersion, appVersion) > 0
        ) {
            persistedVersion
        } else {
            null
        }
    }

    private fun restorePersistedState(context: Context) {
        val preferences = prefs(context)
        val restoredId = preferences.getLong(KEY_DOWNLOAD_ID, -1L).takeIf { it >= 0L }
        val restoredPath = preferences.getString(KEY_APK_PATH, null)?.takeIf { it.isNotBlank() }
        val restoredVersion = preferences.getString(KEY_APK_VERSION, null)?.takeIf { it.isNotBlank() }

        activeDownloadId = restoredId
        activeApkPath = restoredPath
        activeApkVersion = restoredVersion

        if (restoredId != null) {
            sduLog("Restored active update download id=$restoredId")
            if (_updateState.value !is UpdateState.Downloading) {
                _updateState.value = UpdateState.Downloading(_downloadProgress.value.coerceIn(0, 100))
            }
            startDownloadMonitor(context)
            return
        }

        if (!restoredPath.isNullOrBlank()) {
            val apk = File(restoredPath)
            if (apk.exists()) {
                // Показываем баннер только если сохранённая версия НОВЕЕ текущей
                val isNewer = !restoredVersion.isNullOrBlank() &&
                        compareVersions(restoredVersion, appVersion) > 0
                if (isNewer) {
                    _downloadProgress.value = 100
                    _updateState.value = UpdateState.ReadyToInstall(apk.absolutePath)
                } else {
                    // APK уже устарел (юзер обновился или скачал ту же версию) — удаляем
                    apk.delete()
                    activeApkPath = null
                    activeApkVersion = null
                    persistState(context)
                }
            } else {
                activeApkPath = null
                activeApkVersion = null
                persistState(context)
            }
        }
    }

    private fun buildApkFile(context: Context, version: String): File {
        val updatesDir =
            context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
                ?: File(context.filesDir, "updates")
        if (!updatesDir.exists()) {
            updatesDir.mkdirs()
        }
        return File(updatesDir, "Rosetta-$version.apk")
    }

    private fun startDownloadMonitor(context: Context) {
        val downloadId = activeDownloadId ?: return
        val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
        downloadMonitorJob?.cancel()
        downloadMonitorJob =
            scope.launch {
                while (isActive) {
                    val query = DownloadManager.Query().setFilterById(downloadId)
                    downloadManager.query(query).use { cursor ->
                        if (cursor == null || !cursor.moveToFirst()) {
                            activeDownloadId = null
                            persistState(context)
                            setUpdateAvailableOrIdleState()
                            _downloadProgress.value = 0
                            return@launch
                        }

                        val status =
                            cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
                        val downloadedBytes =
                            cursor.getLong(
                                cursor.getColumnIndexOrThrow(
                                    DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR
                                )
                            )
                        val totalBytes =
                            cursor.getLong(
                                cursor.getColumnIndexOrThrow(
                                    DownloadManager.COLUMN_TOTAL_SIZE_BYTES
                                )
                            )

                        when (status) {
                            DownloadManager.STATUS_PENDING,
                            DownloadManager.STATUS_RUNNING,
                            DownloadManager.STATUS_PAUSED -> {
                                val progress =
                                    if (totalBytes > 0L) {
                                        ((downloadedBytes * 100L) / totalBytes).toInt()
                                            .coerceIn(0, 100)
                                    } else {
                                        _downloadProgress.value.coerceIn(0, 99)
                                    }
                                _downloadProgress.value = progress
                                _updateState.value = UpdateState.Downloading(progress)
                            }

                            DownloadManager.STATUS_SUCCESSFUL -> {
                                val localUri =
                                    cursor.getString(
                                        cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI)
                                    )
                                val resolvedPath =
                                    runCatching { Uri.parse(localUri).path }.getOrNull()
                                        ?.takeIf { !it.isNullOrBlank() }
                                        ?: activeApkPath
                                if (!resolvedPath.isNullOrBlank() && File(resolvedPath).exists()) {
                                    activeApkPath = resolvedPath
                                    activeDownloadId = null
                                    persistState(context)
                                    _downloadProgress.value = 100
                                    _updateState.value = UpdateState.ReadyToInstall(resolvedPath)
                                } else {
                                    activeDownloadId = null
                                    persistState(context)
                                    _updateState.value =
                                        UpdateState.Error("Downloaded file is missing")
                                }
                                return@launch
                            }

                            DownloadManager.STATUS_FAILED -> {
                                val reason =
                                    cursor.getInt(
                                        cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON)
                                    )
                                activeDownloadId = null
                                persistState(context)
                                _updateState.value =
                                    UpdateState.Error("Download failed (reason=$reason)")
                                return@launch
                            }
                        }
                    }

                    delay(500L)
                }
            }
    }

    /**
     * Парсинг JSON ответа от SDU /updates/all
     * Формат: {"items":[{"platform":"android","arch":"x64","version":"1.0.7","downloadUrl":"/kernel/android/x64/Rosetta-1.0.7.apk"}, ...]}
     * Ищем запись с platform=android, берём самую новую версию
     */
    private fun parseUpdateResponse(json: String): UpdateInfo {
        val obj = org.json.JSONObject(json)
        val items = obj.optJSONArray("items")

        if (items == null || items.length() == 0) {
            sduLog("No items in response, trying legacy format...")
            // Fallback на старый формат /updates/get
            val version = obj.optString("version", "")
            val rawServicePackUrl =
                obj.optString("service_pack_url", "").takeIf { it.isNotEmpty() && it != "null" }
            val hasNewerVersion =
                version.isNotBlank() && compareVersions(version, appVersion) > 0
            return UpdateInfo(
                version = version,
                platform = obj.optString("platform", ""),
                arch = obj.optString("arch", ""),
                kernelUpdateRequired = obj.optBoolean("kernel_update_required", false),
                servicePackUrl = rawServicePackUrl?.takeIf { hasNewerVersion },
                kernelUrl = obj.optString("kernel_url", "").takeIf { it.isNotEmpty() && it != "null" }
            )
        }

        // Ищем все android-записи
        var bestVersion: String? = null
        var bestDownloadUrl: String? = null
        var bestArch: String? = null

        val deviceArch = Build.SUPPORTED_ABIS.firstOrNull() ?: "arm64-v8a"
        // Маппинг ABI -> SDU arch
        val sduArch = when {
            deviceArch.contains("arm64") -> "arm64"
            deviceArch.contains("arm") -> "arm"
            deviceArch.contains("x86_64") -> "x64"
            deviceArch.contains("x86") -> "x86"
            else -> deviceArch
        }

        sduLog("Looking for platform=android, preferring arch=$sduArch (device ABI=$deviceArch)")

        for (i in 0 until items.length()) {
            val item = items.getJSONObject(i)
            val platform = item.optString("platform", "")
            if (platform != "android") continue

            val itemArch = item.optString("arch", "")
            val itemVersion = item.optString("version", "")
            val itemUrl = item.optString("downloadUrl", "")

            sduLog("Found android entry: arch=$itemArch, version=$itemVersion, url=$itemUrl")

            // Берём самую новую версию (предпочитаем совпадение по arch)
            if (bestVersion == null || compareVersions(itemVersion, bestVersion) > 0 ||
                (compareVersions(itemVersion, bestVersion) == 0 && itemArch == sduArch)) {
                bestVersion = itemVersion
                bestDownloadUrl = itemUrl
                bestArch = itemArch
            }
        }

        if (bestVersion == null) {
            sduLog("No android entries found in items")
            return UpdateInfo("", "android", "", false, null, null)
        }

        sduLog("Best android update: version=$bestVersion, arch=$bestArch, url=$bestDownloadUrl")

        // Сравниваем с текущей версией
        val isNewer = compareVersions(bestVersion, appVersion) > 0
        sduLog("Current=$appVersion, available=$bestVersion, isNewer=$isNewer")

        return UpdateInfo(
            version = bestVersion,
            platform = "android",
            arch = bestArch ?: "",
            kernelUpdateRequired = false,
            servicePackUrl = if (isNewer) bestDownloadUrl else null,
            kernelUrl = null
        )
    }

    /**
     * Сравнение версий: "1.0.7" vs "1.0.6" -> 1 (первая больше)
     */
    private fun compareVersions(v1: String, v2: String): Int {
        val parts1 = v1.split(".").map { it.toIntOrNull() ?: 0 }
        val parts2 = v2.split(".").map { it.toIntOrNull() ?: 0 }
        val maxLen = maxOf(parts1.size, parts2.size)
        for (i in 0 until maxLen) {
            val p1 = parts1.getOrElse(i) { 0 }
            val p2 = parts2.getOrElse(i) { 0 }
            if (p1 != p2) return p1.compareTo(p2)
        }
        return 0
    }
}

/**
 * Информация об обновлении от SDU сервера
 */
data class UpdateInfo(
    val version: String,
    val platform: String,
    val arch: String,
    val kernelUpdateRequired: Boolean,
    val servicePackUrl: String?,
    val kernelUrl: String?
)

/**
 * Состояния процесса обновления
 */
sealed class UpdateState {
    /** Начальное состояние */
    data object Idle : UpdateState()

    /** Проверяем наличие обновлений */
    data object Checking : UpdateState()

    /** Приложение обновлено */
    data object UpToDate : UpdateState()

    /** Доступно обновление */
    data class UpdateAvailable(val version: String) : UpdateState()

    /** Скачивание APK */
    data class Downloading(val progress: Int) : UpdateState()

    /** Готово к установке */
    data class ReadyToInstall(val apkPath: String) : UpdateState()

    /** Ошибка */
    data class Error(val message: String) : UpdateState()
}
