package de.storchp.fdroidbuildstatus

import android.annotation.SuppressLint
import android.app.SearchManager
import android.content.DialogInterface
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.drawable.InsetDrawable
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.Parcelable
import android.util.Log
import android.util.TypedValue
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.AdapterView.OnItemLongClickListener
import android.widget.CompoundButton
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.google.android.material.chip.Chip
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import de.storchp.fdroidbuildstatus.adapter.ApiResponse
import de.storchp.fdroidbuildstatus.adapter.db.DbAdapter
import de.storchp.fdroidbuildstatus.adapter.fdroid.BuildResult
import de.storchp.fdroidbuildstatus.adapter.fdroid.FdroidClient
import de.storchp.fdroidbuildstatus.adapter.fdroid.RunningResult
import de.storchp.fdroidbuildstatus.adapter.fdroid.UpdateResult
import de.storchp.fdroidbuildstatus.databinding.ActivityMainBinding
import de.storchp.fdroidbuildstatus.model.App
import de.storchp.fdroidbuildstatus.model.BuildCycle
import de.storchp.fdroidbuildstatus.model.StatusFilter
import de.storchp.fdroidbuildstatus.monitor.scheduleMonitorJob
import de.storchp.fdroidbuildstatus.utils.NotificationUtils
import de.storchp.fdroidbuildstatus.utils.PreferencesService
import de.storchp.fdroidbuildstatus.utils.Time
import de.storchp.fdroidbuildstatus.utils.addInsetsListener
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject

private const val VISIBLE_THRESHOLD = 10
private const val APP_LIST_PAGE_SIZE = 100

const val EXTRA_BUILD_ITEM_ID = "EXTRA_BUILD_ITEM_ID"
const val EXTRA_BUILD_ITEM_VERSION_CODE = "EXTRA_BUILD_ITEM_VERSION_CODE"
private val TAG = MainActivity::class.java.simpleName
private const val LIST_INSTANCE_STATE = "LIST_INSTANCE_STATE"

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var preferencesService: PreferencesService

    @Inject
    lateinit var dbAdapter: DbAdapter

    @Inject
    lateinit var fdroidClient: FdroidClient

    private lateinit var binding: ActivityMainBinding
    private lateinit var headerAdapter: MainAppListHeaderAdapter
    private lateinit var appListAdapter: MainAppListAdapter
    private val runningFetches = AtomicInteger(0)
    private var lastSearch: String? = null
    private var recyclerViewState: Bundle? = null

    private val detailActivityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { _: ActivityResult? ->
        reloadList()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val listChipPaddingBottom =
            resources.getDimensionPixelSize(R.dimen.list_chip_padding_bottom)
        binding.buildList.addInsetsListener {
            updatePadding(
                left = it.left,
                right = it.right,
                bottom = it.bottom + listChipPaddingBottom
            )
        }
        binding.chipScrollView.addInsetsListener {
            updatePadding(bottom = it.bottom)
        }
        binding.topAppBar.addInsetsListener {
            updatePadding(left = it.left, right = it.right)
        }
        setContentView(binding.root)

        headerAdapter = MainAppListHeaderAdapter()
        appListAdapter = MainAppListAdapter(this, onItemClickListener, onItemLongClickListener)
        val concatAdapter = ConcatAdapter(headerAdapter, appListAdapter)
        binding.buildList.adapter = concatAdapter
        binding.buildList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)

                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                val totalItemCount = layoutManager.itemCount
                val lastVisibleItem = layoutManager.findLastVisibleItemPosition()

                if (totalItemCount <= lastVisibleItem + VISIBLE_THRESHOLD) {
                    loadMoreItems()
                }
            }
        })

        NotificationUtils.cancelNewBuildNotification(this)
        if (intent != null) {
            onNewIntent(intent)
        }
        if (!preferencesService.isRepoIndexLoaded()) {
            loadIndex()
        }
        binding.chipCycle.setOnClickListener { v: View -> showBuildCycleMenu(v) }
        binding.chipStatus.setOnClickListener { v: View -> showBuildStatusMenu(v) }
        binding.pullToRefresh.setOnRefreshListener {
            loadData(true)
            binding.pullToRefresh.isRefreshing = false
        }
        binding.pullToRefresh.apply {
            setProgressBackgroundColorSchemeColor(
                MaterialColors.getColor(
                    this,
                    com.google.android.material.R.attr.colorPrimaryContainer
                )
            )
            setColorSchemeColors(
                MaterialColors.getColor(
                    this,
                    com.google.android.material.R.attr.colorOnPrimaryContainer
                )
            )
        }

        setSupportActionBar(binding.topAppBar)
    }

    private val onItemClickListener =
        OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
            val app = appListAdapter.getItemForPosition(position)
            if (app != null) {
                val detailIntent = Intent(this, DetailActivity::class.java)
                detailIntent.putExtra(EXTRA_BUILD_ITEM_ID, app.id)
                detailActivityResultLauncher.launch(detailIntent)
            }
        }
    private val onItemLongClickListener =
        OnItemLongClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
            val app = appListAdapter.getItemForPosition(position)
            if (app != null) {
                if (getString(R.string.not_found_build_item_name) == app.name) {
                    MaterialAlertDialogBuilder(this@MainActivity)
                        .setIcon(R.mipmap.ic_launcher)
                        .setTitle(R.string.app_name)
                        .setMessage(
                            applicationContext.getString(
                                R.string.add_not_found_app_as_favourite,
                                app.id
                            )
                        )
                        .setNeutralButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
                            toggleFavourite(app, position)
                        }
                        .create().show()
                } else {
                    toggleFavourite(app, position)
                }
            }
            true
        }

    private fun showBuildCycleMenu(v: View) {
        val popup = PopupMenu(this, v)
        popup.menuInflater.inflate(R.menu.menu_buildcycle, popup.menu)
        popup.setOnMenuItemClickListener { menuItem: MenuItem ->
            when (menuItem.itemId) {
                R.id.build_cycle_running -> {
                    preferencesService.setBuildCycleFilter(BuildCycle.RUNNING)
                }

                R.id.build_cycle_build -> {
                    preferencesService.setBuildCycleFilter(BuildCycle.BUILD)
                }

                R.id.build_cycle_update -> {
                    preferencesService.setBuildCycleFilter(BuildCycle.UPDATE)
                }

                else -> {
                    preferencesService.setBuildCycleFilter(BuildCycle.NONE)
                }
            }
            setChipCycle()
            loadData()
            false
        }
        setPopupMenuIcons(popup)
        popup.setOnDismissListener {
            setCloseIcon(
                binding.chipCycle, R.drawable.ic_baseline_arrow_drop_up_24
            )
        }
        popup.show()
        setCloseIcon(binding.chipCycle, R.drawable.ic_baseline_arrow_drop_down_24)
    }

    private fun setCloseIcon(chip: Chip, icon: Int) {
        chip.closeIcon = AppCompatResources.getDrawable(this, icon)
    }

    private fun showBuildStatusMenu(v: View) {
        val popup = PopupMenu(this, v)
        popup.menuInflater.inflate(R.menu.menu_buildstatus, popup.menu)
        popup.setOnMenuItemClickListener { menuItem: MenuItem ->
            when (menuItem.itemId) {
                R.id.build_status_builds -> {
                    preferencesService.setStatusFilter(StatusFilter.SUCCESSFUL_BUILD)
                }

                R.id.build_status_failed -> {
                    preferencesService.setStatusFilter(StatusFilter.FAILED_BUILD)
                }

                R.id.build_status_missing -> {
                    preferencesService.setStatusFilter(StatusFilter.MISSING_BUILD)
                }

                R.id.status_disabled -> {
                    preferencesService.setStatusFilter(StatusFilter.DISABLED)
                }

                R.id.status_archived -> {
                    preferencesService.setStatusFilter(StatusFilter.ARCHIVED)
                }

                R.id.status_needs_update -> {
                    preferencesService.setStatusFilter(StatusFilter.NEEDS_UPDATE)
                }

                R.id.status_no_packages -> {
                    preferencesService.setStatusFilter(StatusFilter.NO_PACKAGES)
                }

                R.id.status_no_update_check -> {
                    preferencesService.setStatusFilter(StatusFilter.NO_UPDATE_CHECK)
                }

                else -> {
                    preferencesService.setStatusFilter(StatusFilter.NONE)
                }
            }
            setChipStatus()
            loadData()
            false
        }
        setPopupMenuIcons(popup)
        popup.setOnDismissListener {
            setCloseIcon(
                binding.chipStatus, R.drawable.ic_baseline_arrow_drop_up_24
            )
        }
        popup.show()
        setCloseIcon(binding.chipStatus, R.drawable.ic_baseline_arrow_drop_down_24)
    }

    @SuppressLint("RestrictedApi")
    private fun setPopupMenuIcons(popup: PopupMenu) {
        try {
            val menuBuilder = popup.menu
            if (menuBuilder is MenuBuilder) {
                menuBuilder.setOptionalIconsVisible(true)
                for (item in menuBuilder.visibleItems) {
                    val iconMarginPx = TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP,
                        0f,
                        resources.displayMetrics
                    )
                    val icon =
                        InsetDrawable(item.icon, iconMarginPx, 0f, iconMarginPx, 0f)
                    item.icon = icon
                }
            }
        } catch (e: Exception) {
            Log.w(TAG, "Error setting popupMenuIcons: ", e)
        }
    }

    private fun loadIndex() {
        MaterialAlertDialogBuilder(this@MainActivity)
            .setIcon(R.mipmap.ic_launcher)
            .setTitle(R.string.app_name)
            .setMessage(R.string.load_index_question)
            .setNegativeButton(R.string.not_now) { _: DialogInterface?, _: Int ->
                preferencesService.setRepoIndexLoaded()
            }
            .setNeutralButton(R.string.download) { _: DialogInterface?, _: Int ->
                fetchIndexWithWorker()
            }
            .create()
            .show()
    }

    private fun fetchIndexWithWorker() {
        startProgress()
        val workRequest = OneTimeWorkRequestBuilder<DownloadFdroidIndexWorker>().build()
        WorkManager.getInstance(this).enqueue(workRequest)
        WorkManager.getInstance(this)
            .getWorkInfoByIdLiveData(workRequest.id)
            .observe(this) { workInfo ->
                if (workInfo?.state == WorkInfo.State.SUCCEEDED) {
                    stopProgress()
                    Toast.makeText(
                        baseContext,
                        getString(
                            R.string.load_index_success,
                            workInfo.outputData.getInt("appsLoaded", 0)
                        ),
                        Toast.LENGTH_LONG
                    ).show()
                } else if (workInfo?.state == WorkInfo.State.FAILED) {
                    stopProgress()
                    Toast.makeText(
                        baseContext,
                        getString(
                            R.string.load_index_failed,
                            workInfo.outputData.getString("errorMessage")
                        ),
                        Toast.LENGTH_LONG
                    ).show()
                }
            }
    }

    override fun onResume() {
        super.onResume()
        setChipCycle()
        setChipStatus()
        val checked = preferencesService.isFavouritesFilter()
        binding.chipFavourite.isChecked = checked
        binding.chipFavourite.setOnCheckedChangeListener { _: CompoundButton?, _: Boolean -> toggleFavouriteFilter() }
        loadData()
        recyclerViewState?.let { viewState ->
            binding.buildList.layoutManager?.onRestoreInstanceState(
                viewState.parcelable(LIST_INSTANCE_STATE)
            )
        }
    }

    inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? = when {
        SDK_INT >= 33 -> getParcelable(key, T::class.java)
        else -> @Suppress("DEPRECATION") getParcelable(key) as? T
    }

    private fun getChipForegroundColor(view: View, active: Boolean): ColorStateList =
        if (active) {
            val color = MaterialColors.getColor(
                view,
                com.google.android.material.R.attr.colorPrimaryContainer
            )
            ColorStateList.valueOf(color)
        } else {
            val color =
                MaterialColors.getColor(view, com.google.android.material.R.attr.colorSurface)
            ColorStateList.valueOf(color)
        }

    private fun getChipStrokeWidth(active: Boolean): Float =
        if (active) {
            0f
        } else {
            resources.getDimension(R.dimen.chip_stroke_width)
        }

    private fun setChipStatus() {
        val statusFilter = preferencesService.getStatusFilter()
        binding.chipStatus.setText(statusFilter.textRes)
        binding.chipStatus.chipBackgroundColor = getChipForegroundColor(binding.chipStatus, statusFilter.isActive)
        binding.chipStatus.chipStrokeWidth = getChipStrokeWidth(statusFilter.isActive)
        binding.chipStatus.isChipIconVisible = statusFilter.isActive
        binding.chipStatus.chipIcon = if (statusFilter.iconRes != 0) {
            ContextCompat.getDrawable(this, statusFilter.iconRes)
        } else {
            null
        }
    }

    private fun setChipCycle() {
        val buildCycleFilter = preferencesService.getBuildCycleFilter()
        binding.chipCycle.text = getString(buildCycleFilter.labelRes)
        binding.chipCycle.chipBackgroundColor = getChipForegroundColor(binding.chipCycle, buildCycleFilter.isFilter)
        binding.chipCycle.chipStrokeWidth = getChipStrokeWidth(buildCycleFilter.isFilter)
        binding.chipCycle.isChipIconVisible = buildCycleFilter.isFilter
        binding.chipCycle.chipIcon = if (buildCycleFilter.iconRes != 0) {
            ContextCompat.getDrawable(this, buildCycleFilter.iconRes)
        } else {
            null
        }
    }

    private fun toggleFavouriteFilter() {
        val active = !preferencesService.isFavouritesFilter()
        preferencesService.setFavouritesFilter(active)
        binding.chipFavourite.isChipIconVisible = active
        loadData()
    }

    private fun toggleFavourite(item: App, position: Int) {
        item.favourite = !item.favourite
        dbAdapter.toggleFavourite(item.id)
        appListAdapter.notifyItemChanged(position - 1)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        if (Intent.ACTION_SEARCH == intent.action) {
            lastSearch = intent.getStringExtra(SearchManager.QUERY)
        } else if (Intent.ACTION_SEND == intent.action) {
            val text = intent.getStringExtra(Intent.EXTRA_TEXT)
            if (text != null) {
                try {
                    lastSearch = getPackageNameFromUri(text.toUri())
                } catch (_: Exception) {
                    Log.e(TAG, "Error reading uri: $text")
                }
            }
        } else if (intent.data != null && Intent.ACTION_VIEW == intent.action) {
            val uri = intent.data
            if (uri!!.isHierarchical) {
                lastSearch = getPackageNameFromUri(uri)
            } else if ("fdroid.app" == uri.scheme) {
                // fdroid.app:app.id
                lastSearch = uri.schemeSpecificPart
            }
        }
        if (lastSearch != null && lastSearch!!.endsWith("/")) {
            lastSearch = lastSearch!!.substring(0, lastSearch!!.length - 1)
        }
    }

    private fun loadData(forceUpdate: Boolean = false) {
        if (isInProgress()) {
            return
        }
        reloadList()
        if (!forceUpdate && System.currentTimeMillis() - preferencesService.getLastUpdateLoaded() < Time.ONE_HOUR) {
            return
        }
        startProgress()
        fdroidClient.getUpdate { response: ApiResponse<UpdateResult?> ->
            val result = response.value()
            if (response.status() == ApiResponse.Status.SUCCESS) {
                dbAdapter.saveUpdate(result!!, false)
                reloadList()
            } else {
                Toast.makeText(
                    baseContext,
                    getString(R.string.update_failed, response.errorMessage()),
                    Toast.LENGTH_LONG
                ).show()
            }
            stopProgress()
        }
        startProgress()
        fdroidClient.getBuild { response: ApiResponse<BuildResult?> ->
            val buildRun = response.value()
            if (response.status() == ApiResponse.Status.SUCCESS) {
                dbAdapter.saveBuildRun(buildRun, BuildCycle.BUILD)
                reloadList()
            } else {
                Toast.makeText(
                    baseContext,
                    getString(R.string.update_failed, response.errorMessage()),
                    Toast.LENGTH_LONG
                ).show()
            }
            stopProgress()
        }
        startProgress()
        fdroidClient.getRunning { response: ApiResponse<RunningResult?> ->
            if (response.status() == ApiResponse.Status.SUCCESS) {
                val result = response.value()
                if (result is BuildResult) {
                    dbAdapter.saveBuildRun(result, BuildCycle.RUNNING)
                    reloadList()
                } else if (result is UpdateResult) {
                    dbAdapter.saveUpdate(result, true)
                    reloadList()
                }
            } else {
                Toast.makeText(
                    baseContext,
                    getString(R.string.update_failed, response.errorMessage()),
                    Toast.LENGTH_LONG
                ).show()
            }
            stopProgress()
        }
        preferencesService.setLastUpdateLoadedNow()
    }

    private fun reloadList() {
        headerAdapter.setBuildStatusText(dbAdapter.loadBuildRuns())

        // load all favourites at once
        val appList = dbAdapter.loadAppBuilds(
            buildCycle = preferencesService.getBuildCycleFilter(),
            favouritesFilter = true,
            statusFilter = preferencesService.getStatusFilter(),
            search = lastSearch,
            lastAppId = null,
            limit = Int.MAX_VALUE,
        )
        // page over non-favourites
        if (!preferencesService.isFavouritesFilter()) {
            appList.addAll(
                dbAdapter.loadAppBuilds(
                    buildCycle = preferencesService.getBuildCycleFilter(),
                    favouritesFilter = false,
                    statusFilter = preferencesService.getStatusFilter(),
                    search = lastSearch,
                    lastAppId = null,
                    limit = APP_LIST_PAGE_SIZE,
                )
            )
        }
        appListAdapter.appList = appList
    }

    private fun loadMoreItems() {
        if (!preferencesService.isFavouritesFilter()) {
            appListAdapter.addItems(
                dbAdapter.loadAppBuilds(
                    buildCycle = preferencesService.getBuildCycleFilter(),
                    favouritesFilter = false,
                    statusFilter = preferencesService.getStatusFilter(),
                    search = lastSearch,
                    lastAppId = appListAdapter.lastAppId,
                    limit = APP_LIST_PAGE_SIZE
                ),
            )
        }
    }

    private fun startProgress() {
        runningFetches.incrementAndGet()
        binding.progressBar.visibility = View.VISIBLE
        binding.pullToRefresh.isEnabled = false
        binding.chipCycle.isEnabled = false
        binding.chipStatus.isEnabled = false
        binding.chipFavourite.isEnabled = false
    }

    private fun isInProgress(): Boolean {
        return runningFetches.get() > 0
    }

    private fun stopProgress() {
        if (runningFetches.decrementAndGet() == 0) {
            binding.progressBar.visibility = View.GONE
            binding.pullToRefresh.isEnabled = true
            binding.chipCycle.isEnabled = true
            binding.chipStatus.isEnabled = true
            binding.chipFavourite.isEnabled = true
        }
    }


    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        val searchMenu = menu.findItem(R.id.action_search)
        searchMenu.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
            override fun onMenuItemActionExpand(arg0: MenuItem): Boolean {
                return true
            }

            override fun onMenuItemActionCollapse(arg0: MenuItem): Boolean {
                invalidateOptionsMenu()
                return true
            }
        })
        val searchView = (searchMenu.actionView as SearchView?)!!
        searchView.setSearchableInfo(
            (getSystemService(SEARCH_SERVICE) as SearchManager).getSearchableInfo(
                componentName
            )
        )
        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(s: String): Boolean {
                Log.d(TAG, "onQueryTextSubmit: $s")
                searchList(s)
                return false
            }

            override fun onQueryTextChange(s: String): Boolean {
                Log.d(TAG, "onQueryTextChange: $s")
                searchList(s)
                return false
            }
        })
        return true
    }

    private fun searchList(search: String?) {
        lastSearch = search?.trim { it <= ' ' }
        reloadList()
    }

    private var settingsActivityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result: ActivityResult ->
        if (result.resultCode == RESULT_OK) {
            loadData()
            // update monitoring service after setting changes
            scheduleMonitorJob(this@MainActivity, preferencesService)
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.action_settings -> {
                settingsActivityResultLauncher.launch(Intent(this, SettingsActivity::class.java))
                return true
            }

            R.id.action_refresh -> {
                loadData(true)
                return true
            }

            R.id.action_website_build_status -> {
                val websiteBuildStatusFragment = WebsiteBuildStatusFragment()
                websiteBuildStatusFragment.show(supportFragmentManager, "WEBSITE_BUILD_STATUS")
                return true
            }

            R.id.action_legend -> {
                val legendFragment = LegendFragment()
                legendFragment.show(supportFragmentManager, "LEGEND")
                return true
            }

            R.id.action_about -> {
                val appInfoFragment = AppInfoFragment()
                appInfoFragment.show(supportFragmentManager, "APP_INFO")
                return true
            }

            R.id.action_load_index -> {
                loadIndex()
            }
        }
        return super.onOptionsItemSelected(item)
    }

    public override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        //outState.putParcelable(LIST_INSTANCE_STATE, binding.buildList.onSaveInstanceState())
    }

    override fun onPause() {
        super.onPause()

        recyclerViewState = Bundle()
        recyclerViewState!!.putParcelable(
            LIST_INSTANCE_STATE,
            binding.buildList.layoutManager?.onSaveInstanceState()
        )
    }

}

private fun getPackageNameFromUri(uri: Uri?): String? {
    val host = uri!!.host
    val path = uri.path
    if (host == null) {
        return null
    }
    when (host) {
        "f-droid.org", "www.f-droid.org", "staging.f-droid.org" -> {
            if (path != null && (path.startsWith("/app/") || path.startsWith("/packages/")
                        || path.matches("^/[a-z][a-z][a-zA-Z_-]*/packages/.*".toRegex()))
            ) {
                // http://f-droid.org/app/packageName
                return uri.lastPathSegment
            }
        }

        "details" -> {
            // market://details?id=app.id
            return uri.getQueryParameter("id")
        }
    }
    return null
}
