package pl.firmatpp.kierowca.sync

import android.content.Context
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.data.ApiErrorKind
import pl.firmatpp.kierowca.data.ApiErrorMapper
import pl.firmatpp.kierowca.data.sync.DriverSyncRepository
import pl.firmatpp.kierowca.diagnostics.AppDiagnostics

class DriverSyncWorker(
    appContext: Context,
    params: WorkerParameters,
) : CoroutineWorker(appContext, params) {
    private val syncRepository = DriverSyncRepository(appContext)

    override suspend fun doWork(): Result =
        runCatching {
            val date = inputData.getString(KEY_DATE)
            val routeId = inputData.getString(KEY_ROUTE_ID)
            val response = syncRepository.fetchSyncState(date, routeId)
            var refreshed = false

            response.scopes.forEach { scope ->
                if (syncRepository.shouldRefresh(scope)) {
                    when (scope.scope) {
                        DriverSyncRepository.SCOPE_ROUTES -> {
                            syncRepository.bootstrap(scope.date ?: date)
                            refreshed = true
                        }
                        DriverSyncRepository.SCOPE_ROUTE_DETAIL -> {
                            val id = scope.routeId ?: routeId
                            if (!id.isNullOrBlank()) {
                                syncRepository.route(id)
                                refreshed = true
                            }
                        }
                        DriverSyncRepository.SCOPE_SETTINGS,
                        DriverSyncRepository.SCOPE_DISPATCH_SHEET -> {
                            syncRepository.bootstrap(scope.date ?: date)
                            refreshed = true
                        }
                        DriverSyncRepository.SCOPE_LEAVE_REQUESTS -> {
                            syncRepository.saveSyncStates(response)
                            refreshed = true
                        }
                    }
                }
            }

            if (!refreshed) {
                syncRepository.saveSyncStates(response)
            }

            Result.success()
        }.getOrElse { throwable ->
            val error = ApiErrorMapper.map(throwable)
            AppDiagnostics.reportNonFatal(
                throwable = throwable,
                operation = "driver_sync_worker",
                keys = mapOf(
                    "date" to inputData.getString(KEY_DATE),
                    "route_id" to inputData.getString(KEY_ROUTE_ID),
                    "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.failure()
        }

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

        fun enqueue(context: Context, date: String?, routeId: String?) {
            val request = OneTimeWorkRequestBuilder<DriverSyncWorker>()
                .setInputData(workDataOf(KEY_DATE to date, KEY_ROUTE_ID to routeId))
                .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
                .build()

            WorkManager.getInstance(context).enqueueUniqueWork(
                listOf("driver-sync", date ?: "-", routeId ?: "-").joinToString("-"),
                ExistingWorkPolicy.REPLACE,
                request,
            )
        }
    }
}
