package com.seven.ikar.kotlin.core.update

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.os.Build
import java.io.File

class ArgusAppUpdateInstaller(private val context: Context) {
    private val preferences = ArgusAppUpdatePreferences(context)

    fun submitInstall(
        packageFile: File,
        update: AvailableIkarUpdate
    ): AppUpdateSubmissionResult {
        val expectedPackageName = update.installedApp.packageName
        val expectedVersionCode = update.manifest.release.androidVersionCode
        val archiveInfo = readArchiveInfo(packageFile)
            ?: return reject(
                expectedPackageName,
                update.manifest.release.version,
                expectedVersionCode,
                "Android не смог прочитать скачанный APK."
            )

        if (archiveInfo.packageName != expectedPackageName) {
            return reject(
                expectedPackageName,
                update.manifest.release.version,
                expectedVersionCode,
                "APK содержит пакет ${archiveInfo.packageName}, а ожидался $expectedPackageName."
            )
        }

        val archiveVersionCode = archiveInfo.versionCodeCompat()
        if (expectedVersionCode != null && archiveVersionCode != expectedVersionCode) {
            return reject(
                expectedPackageName,
                update.manifest.release.version,
                expectedVersionCode,
                "APK содержит versionCode $archiveVersionCode, а Argus ожидал $expectedVersionCode."
            )
        }

        val installer = context.packageManager.packageInstaller
        val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL).apply {
            setAppPackageName(expectedPackageName)
            setSize(packageFile.length())
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                setPackageSource(PackageInstaller.PACKAGE_SOURCE_STORE)
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_REQUIRED)
            }
        }

        val sessionId = installer.createSession(params)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            sessionId,
            Intent(context, ArgusAppUpdateInstallerStatusReceiver::class.java).apply {
                action = ACTION_PACKAGE_STATUS
                putExtra(EXTRA_PACKAGE_NAME, expectedPackageName)
                putExtra(EXTRA_RELEASE_VERSION, update.manifest.release.version)
                if (expectedVersionCode != null) {
                    putExtra(EXTRA_VERSION_CODE, expectedVersionCode)
                }
            },
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
        )

        var session: PackageInstaller.Session? = null
        try {
            session = installer.openSession(sessionId)
            packageFile.inputStream().use { input ->
                session.openWrite("base.apk", 0, packageFile.length()).use { output ->
                    input.copyTo(output)
                    session.fsync(output)
                }
            }
            session.commit(pendingIntent.intentSender)
        } catch (error: Exception) {
            session?.abandon()
            return reject(
                expectedPackageName,
                update.manifest.release.version,
                expectedVersionCode,
                error.message ?: "Android не смог отправить сессию установки."
            )
        } finally {
            session?.close()
        }

        val message = "Запрос на установку обновления отправлен в Android."
        preferences.saveInstallerEvent(
            state = AppUpdateInstallerEventState.Submitted,
            packageName = expectedPackageName,
            releaseVersion = update.manifest.release.version,
            versionCode = expectedVersionCode ?: archiveVersionCode,
            detail = message
        )
        return AppUpdateSubmissionResult.Submitted(message)
    }

    private fun reject(
        packageName: String,
        releaseVersion: String,
        versionCode: Long?,
        message: String
    ): AppUpdateSubmissionResult.Rejected {
        preferences.saveInstallerEvent(
            state = AppUpdateInstallerEventState.Failed,
            packageName = packageName,
            releaseVersion = releaseVersion,
            versionCode = versionCode,
            detail = message
        )
        return AppUpdateSubmissionResult.Rejected(message)
    }

    private fun readArchiveInfo(packageFile: File): PackageInfo? =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            context.packageManager.getPackageArchiveInfo(
                packageFile.absolutePath,
                PackageManager.PackageInfoFlags.of(0)
            )
        } else {
            @Suppress("DEPRECATION")
            context.packageManager.getPackageArchiveInfo(packageFile.absolutePath, 0)
        }

    companion object {
        const val ACTION_PACKAGE_STATUS = "com.seven.ikar.kotlin.PACKAGE_STATUS"
        const val EXTRA_PACKAGE_NAME = "package_name"
        const val EXTRA_RELEASE_VERSION = "release_version"
        const val EXTRA_VERSION_CODE = "version_code"
    }
}

private fun PackageInfo.versionCodeCompat(): Long =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        longVersionCode
    } else {
        @Suppress("DEPRECATION")
        versionCode.toLong()
    }
