package dev.castarr.tv.update

import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.content.FileProvider
import dev.castarr.tv.AppState
import dev.castarr.tv.BuildConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.io.File
import java.net.HttpURLConnection
import java.net.URL

/** In-app updater over Gitea releases (#12). Explicit, never automatic. */
object UpdateChecker {

    private const val TAG = "UpdateChecker"
    private const val LATEST =
        "https://git.beckm4nn.net/api/v1/repos/be-nj/castarr/releases/latest"

    // Release assets need an API token to upload, so the APK is served from
    // an orphan branch instead — anonymously fetchable on a public repo.
    private const val APK_FALLBACK =
        "https://git.beckm4nn.net/be-nj/castarr/raw/branch/apk/castarr.apk"

    private var apkUrl: String = ""

    /** Returns the newer version tag, or null when current. Never throws. */
    suspend fun check(state: AppState): String? = withContext(Dispatchers.IO) {
        runCatching {
            val json = JSONObject(get(LATEST))
            val tag = json.optString("tag_name").removePrefix("v")
            val assets = json.optJSONArray("assets")
            var url = ""
            for (i in 0 until (assets?.length() ?: 0)) {
                val asset = assets!!.getJSONObject(i)
                if (asset.optString("name").endsWith(".apk")) {
                    url = asset.optString("browser_download_url")
                    break
                }
            }
            if (url.isEmpty() && tag.isNotEmpty()) url = APK_FALLBACK
            if (url.isNotEmpty() && isNewer(tag, BuildConfig.VERSION_NAME)) {
                apkUrl = url
                val version = "v$tag"
                withContext(Dispatchers.Main) { state.updateAvailable = version }
                version
            } else {
                withContext(Dispatchers.Main) { state.updateAvailable = null }
                null
            }
        }.onFailure { Log.w(TAG, "check failed: ${it.javaClass.simpleName}") }.getOrNull()
    }

    /** Downloads the APK and hands it to the package installer. */
    suspend fun downloadAndInstall(context: Context, state: AppState): String? =
        withContext(Dispatchers.IO) {
            runCatching {
                if (apkUrl.isEmpty()) check(state)
                require(apkUrl.isNotEmpty()) { "no update available" }
                val dir = File(context.cacheDir, "updates").apply { mkdirs() }
                val file = File(dir, "castarr-update.apk")
                (URL(apkUrl).openConnection() as HttpURLConnection).run {
                    connectTimeout = 15_000
                    readTimeout = 120_000
                    instanceFollowRedirects = true
                    inputStream.use { input -> file.outputStream().use { input.copyTo(it) } }
                    disconnect()
                }
                val uri = FileProvider.getUriForFile(
                    context, "${BuildConfig.APPLICATION_ID}.fileprovider", file,
                )
                val intent = Intent(Intent.ACTION_VIEW).apply {
                    setDataAndType(uri, "application/vnd.android.package-archive")
                    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
                }
                // Launching from the IO dispatcher stalled the installer on
                // the first attempt — activities start from the main thread.
                withContext(Dispatchers.Main) { context.startActivity(intent) }
                null
            }.getOrElse {
                Log.w(TAG, "install failed: ${it.javaClass.simpleName}")
                "Update fehlgeschlagen — später erneut versuchen"
            }
        }

    private fun isNewer(remote: String, local: String): Boolean {
        fun parts(v: String) = v.split(".").mapNotNull { it.toIntOrNull() }
        val r = parts(remote)
        val l = parts(local)
        for (i in 0 until maxOf(r.size, l.size)) {
            val a = r.getOrElse(i) { 0 }
            val b = l.getOrElse(i) { 0 }
            if (a != b) return a > b
        }
        return false
    }

    private fun get(url: String): String {
        val connection = URL(url).openConnection() as HttpURLConnection
        return try {
            connection.connectTimeout = 10_000
            connection.readTimeout = 15_000
            connection.setRequestProperty("Accept", "application/json")
            connection.inputStream.bufferedReader().use { it.readText() }
        } finally {
            connection.disconnect()
        }
    }
}
