package com.meowarex.rlmobile.ui.screens.home

import android.app.Application
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.provider.Settings
import android.util.Log
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.core.graphics.drawable.toBitmap
import androidx.core.net.toUri
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.github.diamondminer88.zip.ZipReader
import com.meowarex.rlmobile.BuildConfig
import com.meowarex.rlmobile.R
import com.meowarex.rlmobile.network.models.GithubCommit
import com.meowarex.rlmobile.network.models.RLBuildInfo
import com.meowarex.rlmobile.network.services.RadiantLyricsGithubService
import com.meowarex.rlmobile.network.utils.CommitsPagingSource
import com.meowarex.rlmobile.network.utils.fold
import com.meowarex.rlmobile.patcher.InstallMetadata
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptions
import com.meowarex.rlmobile.ui.screens.patchopts.PatchOptionsScreen
import com.meowarex.rlmobile.ui.util.TidalVersion
import com.meowarex.rlmobile.util.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream

class HomeModel(
    private val application: Application,
    private val github: RadiantLyricsGithubService,
    private val json: Json,
) : ScreenModel {

    var state by mutableStateOf<HomeState>(HomeState.Loading)
        private set

    val commits = Pager(PagingConfig(pageSize = 30)) {
        CommitsPagingSource(github)
    }.flow.cachedIn(screenModelScope)

    private val refreshingLock = Mutex()
    private var remoteDataJson: RLBuildInfo? = null

    init {
        refresh()
    }

    fun refresh(delay: Boolean = false) = screenModelScope.launchIO {
        if (refreshingLock.isLocked) return@launchIO
        if (delay) {
            delay(250)
            if (refreshingLock.isLocked) return@launchIO
        }

        refreshingLock.withLock {
            val pkg = fetchInstalled()
            val remote = async(Dispatchers.IO) { if (remoteDataJson == null) fetchRemoteData() }
            remote.await()

            val install = pkg?.toInstallData()
            val latest = remoteDataJson?.tidalVersionCode

            mainThread {
                state = HomeState.Loaded(
                    install = install,
                    latestTidalVersionCode = latest,
                )
            }
        }
    }

    fun launchInstall() {
        val current = (state as? HomeState.Loaded)?.install ?: return
        openApp(current.packageName)
    }

    fun openCurrentAppInfo() {
        val current = (state as? HomeState.Loaded)?.install ?: return
        openAppInfo(current.packageName)
    }

    fun createReinstallScreen(): PatchOptionsScreen? {
        val current = (state as? HomeState.Loaded)?.install ?: return null
        return createPrefilledPatchOptsScreen(current.packageName)
    }

    fun openApp(packageName: String) {
        val launchIntent = application.packageManager.getLaunchIntentForPackage(packageName)
        if (launchIntent != null) {
            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            application.startActivity(launchIntent)
        } else {
            application.showToast(R.string.launch_app_fail)
        }
    }

    fun openAppInfo(packageName: String) {
        val launchIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            .setData("package:$packageName".toUri())
        application.startActivity(launchIntent)
    }

    fun createPrefilledPatchOptsScreen(packageName: String): PatchOptionsScreen {
        val metadata = try {
            val applicationInfo = application.packageManager.getApplicationInfo(packageName, 0)
            val metadataFile = ZipReader(applicationInfo.publicSourceDir)
                .use { it.openEntry("rlmobile.json")?.read() }
            metadataFile?.let { json.decodeFromStream<InstallMetadata>(it.inputStream()) }
        } catch (t: Throwable) {
            Log.w(BuildConfig.TAG, "Failed to parse install metadata for $packageName", t)
            null
        }

        val patchOptions = metadata?.options ?: PatchOptions.Default.copy(packageName = packageName)
        return PatchOptionsScreen(prefilledOptions = patchOptions)
    }

    private fun fetchInstalled(): PackageInfo? = application.packageManager
        .getInstalledPackages(PackageManager.GET_META_DATA)
        .firstOrNull { it.applicationInfo?.metaData?.containsKey("isRadiantLyrics") == true }

    private fun PackageInfo.toInstallData(): InstallData {
        val pm = application.packageManager
        @Suppress("DEPRECATION") val versionCode = versionCode
        val versionName = versionName ?: ""
        val info = applicationInfo!!
        return InstallData(
            name = pm.getApplicationLabel(info).toString(),
            packageName = packageName,
            isUpToDate = isInstallationUpToDate(this),
            icon = pm.getApplicationIcon(info).toBitmap().asImageBitmap().let(::BitmapPainter),
            version = TidalVersion.Existing(
                type = TidalVersion.parseVersionType(versionCode),
                name = versionName.split("-")[0].trim(),
                code = versionCode,
            ),
        )
    }

    private suspend fun fetchRemoteData() {
        val release = try {
            github.getLatestRelease().fold(
                success = { it },
                fail = { Log.w(BuildConfig.TAG, "Failed to fetch latest release", it); return },
            )
        } catch (t: Throwable) {
            Log.w(BuildConfig.TAG, "Failed to fetch remote data", t)
            return
        }

        val dataJsonUrl = release.assets
            .find { it.name == RadiantLyricsGithubService.DATA_JSON_ASSET_NAME }
            ?.browserDownloadUrl
            ?: return

        github.getBuildInfo(dataJsonUrl).fold(
            success = { remoteDataJson = it },
            fail = { Log.w(BuildConfig.TAG, "Failed to fetch build info", it) },
        )
    }

    private fun isInstallationUpToDate(pkg: PackageInfo): Boolean? {
        val remote = remoteDataJson ?: return null
        @Suppress("DEPRECATION") val versionCode = pkg.versionCode
        if (remote.tidalVersionCode != versionCode) return false

        val apkPath = pkg.applicationInfo?.publicSourceDir ?: return false
        val installMetadata = try {
            val mf = ZipReader(apkPath).use { it.openEntry("rlmobile.json")?.read() } ?: return false
            json.decodeFromStream<InstallMetadata>(mf.inputStream())
        } catch (t: Throwable) {
            return false
        }

        if (installMetadata.options.customPatches != null) return true
        return remote.patchesVersion == installMetadata.patchesVersion
    }
}
