package cy.hexor.furumi.offline

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import cy.hexor.furumi.MainActivity
import cy.hexor.furumi.data.local.OfflineDownloadQueueStore
import cy.hexor.furumi.data.local.OfflineMediaStore
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@AndroidEntryPoint
class OfflineDownloadJobService : JobService() {
    @Inject lateinit var downloadQueueStore: OfflineDownloadQueueStore
    @Inject lateinit var offlineMediaStore: OfflineMediaStore

    private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private var activeJob: Job? = null
    @Volatile private var stoppedBySystem = false

    override fun onStartJob(params: JobParameters): Boolean {
        stoppedBySystem = false
        publishUserInitiatedJobNotification(params)
        activeJob?.cancel()
        activeJob = serviceScope.launch {
            try {
                runDownloadQueue()
                if (!stoppedBySystem) {
                    jobFinished(params, false)
                }
            } catch (_: CancellationException) {
                if (!stoppedBySystem) {
                    jobFinished(params, true)
                }
            }
        }
        return true
    }

    override fun onStopJob(params: JobParameters): Boolean {
        stoppedBySystem = true
        activeJob?.cancel()
        return true
    }

    override fun onDestroy() {
        activeJob?.cancel()
        serviceScope.cancel()
        super.onDestroy()
    }

    private suspend fun runDownloadQueue() = withContext(Dispatchers.IO) {
        downloadQueueStore.refresh()
        offlineMediaStore.publishStorageStats()

        while (isActive) {
            val track = downloadQueueStore.nextPendingDownloadTrack() ?: break
            downloadQueueStore.markRunning(track.id)
            try {
                offlineMediaStore.downloadTrackForOffline(track)
                downloadQueueStore.markCompleted(track.id)
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                downloadQueueStore.markFailed(track.id, e.message ?: "Unable to download track")
            }
            offlineMediaStore.publishStorageStats()
        }

        offlineMediaStore.publishStorageStats()
    }

    private fun publishUserInitiatedJobNotification(params: JobParameters) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE || !params.isUserInitiatedJob) {
            return
        }

        createNotificationChannel()
        setNotification(
            params,
            NOTIFICATION_ID,
            buildNotification(),
            JOB_END_NOTIFICATION_POLICY_REMOVE
        )
    }

    private fun buildNotification() =
        NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(android.R.drawable.stat_sys_download)
            .setContentTitle("Downloading offline music")
            .setContentText("Saving tracks for offline playback")
            .setContentIntent(contentIntent())
            .setCategory(NotificationCompat.CATEGORY_PROGRESS)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setProgress(0, 0, true)
            .setOnlyAlertOnce(true)
            .setSilent(true)
            .setOngoing(true)
            .build()

    private fun contentIntent(): PendingIntent =
        PendingIntent.getActivity(
            this,
            0,
            Intent(this, MainActivity::class.java),
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return

        val channel = NotificationChannel(
            CHANNEL_ID,
            "Offline downloads",
            NotificationManager.IMPORTANCE_LOW
        ).apply {
            setShowBadge(false)
            enableVibration(false)
            enableLights(false)
            setSound(null, null)
        }
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }

    private companion object {
        const val NOTIFICATION_ID = 1002
        const val CHANNEL_ID = "furumi_offline_downloads"
    }
}
