package pl.firmatpp.kierowca.sync

import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import java.util.concurrent.TimeUnit
import pl.firmatpp.kierowca.MainActivity
import pl.firmatpp.kierowca.R
import pl.firmatpp.kierowca.data.ApiErrorKind
import pl.firmatpp.kierowca.data.ApiErrorMapper
import pl.firmatpp.kierowca.data.DriverRepository
import pl.firmatpp.kierowca.diagnostics.AppDiagnostics
import retrofit2.HttpException

class NewRouteNotificationWorker(
    appContext: Context,
    params: WorkerParameters,
) : CoroutineWorker(appContext, params) {
    private val repository = DriverRepository(appContext)

    override suspend fun doWork(): Result {
        val routeId = inputData.getString(KEY_ROUTE_ID)?.takeIf(String::isNotBlank) ?: return Result.success()

        return runCatching {
            val route = repository.route(routeId).route
            if (!canShowNotifications(applicationContext)) {
                return@runCatching Result.success()
            }

            createChannel(applicationContext)
            val contentTitle = NewRouteNotificationFormatter.title(route.routeDate ?: inputData.getString(KEY_DATE))
            val contentText = NewRouteNotificationFormatter.collapsedText(route)
            val expandedText = NewRouteNotificationFormatter.expandedText(route)
            val intent = Intent(applicationContext, MainActivity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
                putExtra(MainActivity.EXTRA_ROUTE_ID, route.id)
            }
            val pendingIntent = PendingIntent.getActivity(
                applicationContext,
                route.id.hashCode(),
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
            )

            val notification = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(contentTitle)
                .setContentText(contentText)
                .setStyle(NotificationCompat.BigTextStyle().bigText(expandedText))
                .setContentIntent(pendingIntent)
                .setAutoCancel(true)
                .setCategory(NotificationCompat.CATEGORY_REMINDER)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .build()

            NotificationManagerCompat.from(applicationContext).notify(route.id.hashCode(), notification)
            Result.success()
        }.getOrElse { throwable ->
            if (throwable is HttpException && throwable.code() == 404) {
                Result.success()
            } else {
                val error = ApiErrorMapper.map(throwable)
                AppDiagnostics.reportNonFatal(
                    throwable = throwable,
                    operation = "new_route_notification_worker",
                    keys = mapOf(
                        "route_id" to routeId,
                        "api_error_kind" to error.kind.name,
                        "api_error_code" to error.code,
                        "api_status_code" to error.statusCode,
                        "retryable" to error.retryable,
                    ),
                )
                if (error.retryable && error.kind != ApiErrorKind.Auth) Result.retry() else Result.success()
            }
        }
    }

    private fun createChannel(context: Context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return

        val channel = NotificationChannel(
            CHANNEL_ID,
            "Nowe kursy",
            NotificationManager.IMPORTANCE_DEFAULT,
        ).apply {
            description = "Powiadomienia o nowych kursach dodanych przez spedytora."
        }
        context.getSystemService(NotificationManager::class.java)?.createNotificationChannel(channel)
    }

    private fun canShowNotifications(context: Context): Boolean {
        val hasRuntimePermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
            ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED

        return hasRuntimePermission && NotificationManagerCompat.from(context).areNotificationsEnabled()
    }

    companion object {
        private const val CHANNEL_ID = "new_routes"
        private const val KEY_ROUTE_ID = "routeId"
        private const val KEY_DATE = "date"

        fun enqueue(context: Context, routeId: String?, date: String?) {
            if (routeId.isNullOrBlank()) return

            val request = OneTimeWorkRequestBuilder<NewRouteNotificationWorker>()
                .setInputData(workDataOf(KEY_ROUTE_ID to routeId, KEY_DATE to date))
                .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
                .build()

            WorkManager.getInstance(context).enqueueUniqueWork(
                "driver-new-route-notification-$routeId",
                ExistingWorkPolicy.REPLACE,
                request,
            )
        }
    }
}
