package com.remotedisplay.player.service

import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.content.FileProvider
import com.remotedisplay.player.data.ServerConfig
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import java.io.File
import java.security.MessageDigest
import java.util.concurrent.TimeUnit

class UpdateChecker(private val context: Context) {

    private val TAG = "UpdateChecker"
    private val client = OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .build()
    private val handler = Handler(Looper.getMainLooper())
    private val config = ServerConfig(context)
    private var checkTimer: Runnable? = null

    // Check every 30 minutes
    private val CHECK_INTERVAL = 30 * 60 * 1000L

    private var installReceiverRegistered = false

    // The PackageInstaller session reports its status (incl. STATUS_PENDING_USER_ACTION,
    // which Android 13+ returns for non-device-owner installers) via this broadcast.
    // Without handling it the committed session just stalls and the update never
    // installs. On the action prompt we launch the confirm dialog; the accessibility
    // service auto-confirms it on kiosks.
    private fun ensureInstallReceiver() {
        if (installReceiverRegistered) return
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(ctx: Context, intent: Intent) {
                when (intent.getIntExtra(android.content.pm.PackageInstaller.EXTRA_STATUS, -999)) {
                    android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION -> {
                        val confirm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
                            intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
                        else @Suppress("DEPRECATION") intent.getParcelableExtra(Intent.EXTRA_INTENT)
                        if (confirm != null) {
                            confirm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                            try { context.startActivity(confirm); Log.i(TAG, "Launched install confirmation") }
                            catch (e: Exception) { Log.e(TAG, "Confirm launch failed: ${e.message}") }
                        }
                    }
                    android.content.pm.PackageInstaller.STATUS_SUCCESS -> Log.i(TAG, "Update installed successfully")
                    else -> Log.w(TAG, "Install status: ${intent.getStringExtra(android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE)}")
                }
            }
        }
        val filter = IntentFilter("com.remotedisplay.player.INSTALL_COMPLETE")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED)
        } else {
            @Suppress("UnspecifiedRegisterReceiverFlag") context.registerReceiver(receiver, filter)
        }
        installReceiverRegistered = true
    }

    fun startPeriodicCheck() {
        stopPeriodicCheck()
        ensureInstallReceiver()
        checkTimer = object : Runnable {
            override fun run() {
                checkForUpdate()
                handler.postDelayed(this, CHECK_INTERVAL)
            }
        }
        // First check after 60 seconds (let the app settle)
        handler.postDelayed(checkTimer!!, 60000)
        Log.i(TAG, "Periodic update check started (every ${CHECK_INTERVAL / 60000}m)")
    }

    fun stopPeriodicCheck() {
        checkTimer?.let { handler.removeCallbacks(it) }
        checkTimer = null
    }

    fun checkForUpdate() {
        if (config.serverUrl.isEmpty()) return

        Thread {
            try {
                val currentVersion = getAppVersion()
                val url = "${config.serverUrl}/api/update/check?version=$currentVersion"
                Log.i(TAG, "Checking for updates: $url")

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

                if (!response.isSuccessful) {
                    Log.w(TAG, "Update check failed: ${response.code}")
                    return@Thread
                }

                val json = JSONObject(response.body?.string() ?: "{}")
                val updateAvailable = json.optBoolean("update_available", false)
                val latestVersion = json.optString("latest_version", currentVersion)
                val downloadUrl = json.optString("download_url", "")

                Log.i(TAG, "Current: $currentVersion, Latest: $latestVersion, Update: $updateAvailable")

                if (updateAvailable && downloadUrl.isNotEmpty()) {
                    Log.i(TAG, "Update available! Downloading...")
                    downloadAndInstall("${config.serverUrl}$downloadUrl", latestVersion)
                }
            } catch (e: Exception) {
                Log.e(TAG, "Update check error: ${e.message}")
            }
        }.start()
    }

    private fun downloadAndInstall(url: String, version: String) {
        try {
            // Download to a temp file
            val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()

            if (!response.isSuccessful) {
                Log.e(TAG, "Download failed: ${response.code}")
                return
            }

            val apkFile = File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
                "ScreenTinker-$version.apk")

            response.body?.byteStream()?.use { input ->
                apkFile.outputStream().use { output ->
                    input.copyTo(output)
                }
            }

            Log.i(TAG, "APK downloaded: ${apkFile.absolutePath} (${apkFile.length()} bytes)")

            // SECURITY (#5 review): never install an APK we didn't sign. The update
            // is fetched from a server-supplied URL, often over cleartext with no
            // pinning - a MITM or compromised server could otherwise return a
            // malicious APK and get it silently installed (REQUEST_INSTALL_PACKAGES).
            // Verify the downloaded APK is our package AND signed by the same key as
            // the currently-installed app before installing. An attacker can't forge
            // our signature, so this holds even over an untrusted transport.
            if (!verifyApkSignature(apkFile)) {
                Log.e(TAG, "Refusing update: APK signature/package verification failed (tampered or MITM'd APK)")
                apkFile.delete()
                return
            }
            Log.i(TAG, "APK signature verified against installed app - proceeding to install")

            // Install the APK
            handler.post {
                installApk(apkFile)
            }
        } catch (e: Exception) {
            Log.e(TAG, "Download/install error: ${e.message}")
        }
    }

    private fun installApk(apkFile: File) {
        // Try silent session install first (no Play Protect dialog)
        try {
            tryPackageInstaller(apkFile)
            return
        } catch (e: Exception) {
            Log.w(TAG, "Session install failed: ${e.message}, falling back to intent")
        }

        // Fallback: intent-based install (shows dialog)
        try {
            val intent = Intent(Intent.ACTION_VIEW)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                val uri = FileProvider.getUriForFile(
                    context,
                    "${context.packageName}.fileprovider",
                    apkFile
                )
                intent.setDataAndType(uri, "application/vnd.android.package-archive")
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            } else {
                intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive")
            }

            context.startActivity(intent)
            Log.i(TAG, "Install intent launched")
        } catch (e: Exception) {
            Log.e(TAG, "Install failed: ${e.message}")
        }
    }

    private fun tryPackageInstaller(apkFile: File) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                val installer = context.packageManager.packageInstaller
                val params = android.content.pm.PackageInstaller.SessionParams(
                    android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
                )
                val sessionId = installer.createSession(params)
                val session = installer.openSession(sessionId)

                apkFile.inputStream().use { input ->
                    session.openWrite("ScreenTinker", 0, apkFile.length()).use { output ->
                        input.copyTo(output)
                        session.fsync(output)
                    }
                }

                // #96 (install bug): the status PendingIntent must stay FLAG_MUTABLE so
                // PackageInstaller can write EXTRA_STATUS back into it - but on Android 14+
                // (target SDK 34+) a FLAG_MUTABLE PendingIntent with an *implicit* intent is
                // disallowed and getBroadcast() throws, silently aborting every OTA on 14+.
                // Make the intent explicit (setPackage) so mutable is allowed; it also keeps
                // the broadcast to our own RECEIVER_NOT_EXPORTED receiver.
                val pendingIntent = android.app.PendingIntent.getBroadcast(
                    context, sessionId,
                    Intent("com.remotedisplay.player.INSTALL_COMPLETE").setPackage(context.packageName),
                    android.app.PendingIntent.FLAG_MUTABLE
                )
                session.commit(pendingIntent.intentSender)
                Log.i(TAG, "Package installer session committed")
            }
        } catch (e: Exception) {
            Log.e(TAG, "Package installer failed: ${e.message}")
        }
    }

    // True only if the downloaded APK is this same package and shares a signing
    // certificate with the installed app. Fail-closed on any error.
    private fun verifyApkSignature(apkFile: File): Boolean {
        return try {
            val pm = context.packageManager
            val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
                PackageManager.GET_SIGNING_CERTIFICATES else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES
            val downloaded = pm.getPackageArchiveInfo(apkFile.absolutePath, flags)
            if (downloaded == null) {
                Log.e(TAG, "Could not parse downloaded APK")
                return false
            }
            if (downloaded.packageName != context.packageName) {
                Log.e(TAG, "APK package mismatch: ${downloaded.packageName} != ${context.packageName}")
                return false
            }
            val installed = pm.getPackageInfo(context.packageName, flags)
            val downloadedSigs = signingCertHashes(downloaded)
            val installedSigs = signingCertHashes(installed)
            if (downloadedSigs.isEmpty() || installedSigs.isEmpty()) {
                Log.e(TAG, "Missing signing certificates (downloaded=${downloadedSigs.size}, installed=${installedSigs.size})")
                return false
            }
            // Share at least one current signing certificate.
            val match = downloadedSigs.any { it in installedSigs }
            if (!match) Log.e(TAG, "APK signing certificate does not match installed app")
            match
        } catch (e: Exception) {
            Log.e(TAG, "Signature verification error: ${e.message}", e)
            false
        }
    }

    private fun signingCertHashes(info: PackageInfo): Set<String> {
        val sigs: Array<Signature>? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            info.signingInfo?.apkContentsSigners
        } else {
            @Suppress("DEPRECATION") info.signatures
        }
        return sigs?.mapNotNull { sha256(it.toByteArray()) }?.toSet() ?: emptySet()
    }

    private fun sha256(bytes: ByteArray): String? {
        return try {
            MessageDigest.getInstance("SHA-256").digest(bytes).joinToString("") { "%02x".format(it) }
        } catch (e: Exception) {
            null
        }
    }

    private fun getAppVersion(): String {
        return try {
            context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "1.0.0"
        } catch (e: Exception) {
            "1.0.0"
        }
    }
}
