package com.isidroid.job_sender

import android.Manifest
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.hilt.work.HiltWorker
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.isidroid.job_sender.domain.dto.JobSenderResult
import com.isidroid.job_sender.domain.use_case.SendPrintJobsUseCase
import com.isidroid.utils.checkIfNotificationChannelExists
import com.isidroid.utils.createNotificationChannel
import com.isidroid.utils.hasPermission
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.TimeUnit

private const val CHANNEL_ID = "UPLOAD_DATA"

@HiltWorker
class SendJobWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val useCase: SendPrintJobsUseCase,
    private val collector: SendJobEventCollectorFlow
) : CoroutineWorker(appContext, workerParams) {

    init {
        if (appContext.checkIfNotificationChannelExists(CHANNEL_ID) == null)
            applicationContext.createNotificationChannel(channelId = CHANNEL_ID, name = "Upload print jobs", importance = NotificationManager.IMPORTANCE_DEFAULT)
    }

    override suspend fun doWork(): Result = coroutineScope {
        val collectorJob = launch { runCollector() }
        var result: Result?
        var notification: Notification?

        try {
            useCase.start()
            notification = createNotification("Upload successfully completed")
            result = Result.success()
        } catch (t: Throwable) {
            Timber.e(t)
            notification = createNotification("Upload complete with errors")
            result = Result.failure()
        } finally {
            collectorJob.cancel()
        }

        showNotification(notification)
        result ?: Result.failure()
    }

    private suspend fun runCollector() {
        coroutineScope {
            collector.eventsFlow.collect { event ->
                val notification = when (event) {
                    is JobSenderResult.RenderProgress -> createNotification(title = "render", event.position, event.total, true)
                    is JobSenderResult.UploadProgress -> createNotification(title = "upload", event.position, event.total, true)
                    else -> null
                }

                showNotification(notification)
            }
        }
    }


    private fun createNotification(title: String, position: Int? = null, total: Int? = null, isOngoning: Boolean = false): Notification {
        val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_upload_notification)
            .setContentTitle(title)
            .setOngoing(isOngoning)

        if (position != null && total != null)
            builder.setProgress(total, position, false)

        return builder.build()
    }

    @SuppressLint("MissingPermission")
    private fun showNotification(notification: Notification?) {
        notification ?: return
        if (applicationContext.hasPermission(Manifest.permission.POST_NOTIFICATIONS))
            NotificationManagerCompat.from(applicationContext).notify(100, notification)
    }

    companion object {
        fun create(context: Context) {
            val workManager = WorkManager.getInstance(context)

            val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()

            val builder = OneTimeWorkRequestBuilder<SendJobWorker>().keepResultsForAtLeast(1, TimeUnit.SECONDS)
            val data = Data.Builder().build()
            val requestBuilder = builder
                .setConstraints(constraints)
                .setInputData(data)
                .setId(UUID.randomUUID())

            // We use KEEP to avoid duplicate runs of SyncRepository
            workManager.beginUniqueWork(SendJobWorker::class.java.simpleName, ExistingWorkPolicy.REPLACE, requestBuilder.build()).enqueue()
        }
    }
}