package de.storchp.fdroidbuildstatus.monitor

import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.util.Log
import androidx.core.content.ContextCompat
import androidx.work.Configuration
import dagger.hilt.android.AndroidEntryPoint
import de.storchp.fdroidbuildstatus.R
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.PublishedVersions
import de.storchp.fdroidbuildstatus.model.App
import de.storchp.fdroidbuildstatus.model.AppNotification
import de.storchp.fdroidbuildstatus.model.BuildCycle
import de.storchp.fdroidbuildstatus.utils.NotificationUtils
import de.storchp.fdroidbuildstatus.utils.PreferencesService
import javax.inject.Inject

@AndroidEntryPoint
class MonitorJobService : JobService() {

    @Inject
    lateinit var preferencesService: PreferencesService

    @Inject
    lateinit var dbAdapter: DbAdapter

    @Inject
    lateinit var fdroidClient: FdroidClient

    init {
        Configuration.Builder().setJobSchedulerJobIdRange(0, 1000)
    }

    override fun onStartJob(params: JobParameters): Boolean {
        val favorites = dbAdapter.loadFavourites()
        if (favorites.isEmpty()) {
            return false
        }

        if (preferencesService.isNotifyRunningBuilds()) {
            monitorRunning(favorites)
        }

        if (preferencesService.isNotifyFinishedBuilds()) {
            monitorBuild(favorites)
        }

        if (preferencesService.isNotifyPublish()) {
            monitorPublish(favorites)
        }
        return true
    }

    private fun monitorPublish(favorites: Map<String, App>) {
        val notifiedApps = dbAdapter.getNotificationsFor(BuildCycle.PUBLISH, 0)
        favorites.forEach { favorite ->
            fdroidClient.getPublishedVersions(favorite.value.id) { response ->
                val publishedVersions = response.value()
                if (response.isSuccess && publishedVersions != null) {
                    notifyPublishIfNewer(notifiedApps, favorite.value, publishedVersions.packages)
                }
            }
        }
    }

    private fun notifyPublishIfNewer(
        notifiedApps: Set<AppNotification>,
        favorite: App,
        publishedPackages: List<PublishedVersions.PublishedPackage>
    ) {
        val maxNotifiedVersionCode =
            notifiedApps.filter { it.id == favorite.id }.maxOfOrNull { it.versionCode }
        val maxNewVersionCode =
            publishedPackages.map { AppNotification(favorite.id, it.versionCode) }
                .filter { maxNotifiedVersionCode == null || it.versionCode > maxNotifiedVersionCode }
                .maxByOrNull { it.versionCode }
        maxNewVersionCode?.let {
            NotificationUtils.createNewPublishFavoriteNotification(
                this@MonitorJobService,
                getString(R.string.new_fdroid_publish_of, it.versionCode, it.id),
                it.id,
            )
            dbAdapter.saveNotification(BuildCycle.PUBLISH, it)
        }
    }

    private fun monitorRunning(favorites: Map<String, App>) {
        fdroidClient.getRunning { response ->
            if (response.isSuccess) {
                val result = response.value()
                if (result is BuildResult) {
                    processBuildRun(result, BuildCycle.RUNNING, favorites)
                }
            } else {
                Log.e(TAG, "MonitorService failed: " + response.errorMessage())
            }
        }
    }

    private fun monitorBuild(favorites: Map<String, App>) {
        fdroidClient.getBuild { response ->
            val buildRun = response.value()
            if (response.isSuccess && buildRun != null) {
                processBuildRun(buildRun, BuildCycle.BUILD, favorites)
            } else {
                Log.e(TAG, "MonitorService failed: " + response.errorMessage())
            }
        }
    }

    private fun processBuildRun(
        buildRun: BuildResult,
        buildCycle: BuildCycle,
        favorites: Map<String, App>
    ) {
        val oldBuildRun = dbAdapter.loadBuildRuns()[buildCycle]
        if (oldBuildRun == null || oldBuildRun.lastModified != buildRun.lastModified) {
            dbAdapter.saveBuildRun(buildRun, buildCycle)
            notifyBuildsOfFavorites(buildRun, buildCycle, favorites)
        }
    }

    private fun notifyBuildsOfFavorites(
        buildRun: BuildResult,
        buildCycle: BuildCycle,
        favorites: Map<String, App>
    ) {
        val notifiedApps = dbAdapter.getNotificationsFor(buildCycle, buildRun.startTimestamp)
        val newFavoriteApps = buildRun.allBuilds()
            .map { AppNotification(it.id, it.versionCode) }
            .filter { favorites.containsKey(it.id) && !notifiedApps.contains(it) }
            .toSet()
        if (newFavoriteApps.isNotEmpty()) {
            val names = newFavoriteApps
                .mapNotNull { favorites[it.id] }
                .map(App::displayName)
                .distinct()
            NotificationUtils.createNewBuildFavoritesNotification(
                this@MonitorJobService,
                getString(R.string.new_build_notification_favs, names.size),
                names,
            )
            dbAdapter.saveNotifications(
                buildCycle,
                buildRun.startTimestamp,
                notifiedApps + newFavoriteApps
            )
        }
    }

    override fun onStopJob(params: JobParameters): Boolean {
        return true
    }

}

private val TAG = MonitorJobService::class.java.simpleName
private const val JOB_ID = 1000

fun scheduleMonitorJob(context: Context, preferencesService: PreferencesService) {
    val jobScheduler = ContextCompat.getSystemService(context, JobScheduler::class.java)!!
    jobScheduler.cancel(JOB_ID) // cancel previous scheduled job
    if (!preferencesService.isBackgroundNotificationsEnabled()) {
        return
    }
    if (!NotificationUtils.areNotificationsEnabled(context)) {
        preferencesService.disableBackgroundNotifications()
        return
    }
    val updateInterval = preferencesService.getUpdateInterval()
    val jobInfo =
        JobInfo.Builder(JOB_ID, ComponentName(context, MonitorJobService::class.java))
            .setPeriodic(updateInterval)
            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
            .setRequiresBatteryNotLow(true)
            .build()
    jobScheduler.schedule(jobInfo)
}
