package pl.firmatpp.kierowca.data.upload

import android.content.Context
import androidx.work.WorkManager
import java.io.File
import java.util.concurrent.TimeUnit

class OfflineOutboxManager(private val context: Context) {
    private val database = DriverDatabase.get(context)
    private val workManager = WorkManager.getInstance(context)

    suspend fun pendingCount(): Int =
        database.photoUploadDao().allUnsent().size +
            database.dispatchSheetUploadDao().allUnsent().size +
            database.routeActionDao().allUnsent().size +
            database.routePointDao().allUnsent().size

    suspend fun repairGpsQueue() {
        val retryableStatuses = setOf(
            RoutePointStatus.Pending,
            RoutePointStatus.Syncing,
            RoutePointStatus.FailedRetryable,
        )
        val routeIds = database.routePointDao().allUnsent()
            .filter { it.status in retryableStatuses }
            .map { it.routeId }
            .distinct()

        routeIds.forEach { routeId ->
            runCatching {
                workManager.cancelUniqueWork(RoutePointWorker.uniqueWorkName(routeId))
                    .result
                    .get(5, TimeUnit.SECONDS)
            }
            RoutePointOutbox(context).enqueueWorker(routeId, delaySeconds = 0L)
        }
    }

    suspend fun repairOperationalQueues() {
        val photoOutbox = PhotoUploadOutbox(context)
        val dispatchSheetOutbox = DispatchSheetUploadOutbox(context)
        val routeActionOutbox = RouteActionOutbox(context)

        database.photoUploadDao().allUnsent()
            .filter { shouldRepairPhotoUpload(it.status, it.lastError) }
            .forEach { photoOutbox.retry(it.clientRequestId) }

        database.dispatchSheetUploadDao().allUnsent()
            .filter { shouldRepairPhotoUpload(it.status, it.lastError) }
            .forEach { dispatchSheetOutbox.retry(it.clientRequestId) }

        database.routeActionDao().allUnsent()
            .filter { shouldRepairRouteAction(it.status, it.lastError) }
            .forEach { routeActionOutbox.retry(it.clientActionId) }
    }

    suspend fun clearAll() {
        val photos = database.photoUploadDao().allRecords()
        val dispatchSheets = database.dispatchSheetUploadDao().allRecords()
        val actions = database.routeActionDao().allUnsent()
        val points = database.routePointDao().allUnsent()

        photos.forEach {
            workManager.cancelUniqueWork(PhotoUploadWorker.uniqueWorkName(it.clientRequestId))
            File(it.localPath).delete()
        }
        dispatchSheets.forEach {
            workManager.cancelUniqueWork(DispatchSheetUploadWorker.uniqueWorkName(it.clientRequestId))
            File(it.localPath).delete()
        }
        actions.forEach { workManager.cancelUniqueWork(RouteActionWorker.uniqueWorkName(it.clientActionId)) }
        points.map { it.routeId }.distinct().forEach {
            workManager.cancelUniqueWork(RoutePointWorker.uniqueWorkName(it))
        }

        database.photoUploadDao().deleteAll()
        database.dispatchSheetUploadDao().deleteAll()
        database.routeActionDao().deleteAll()
        database.routePointDao().deleteAll()
    }
}

internal fun isWorkerCancellationFailure(lastError: String?): Boolean =
    lastError?.let { error ->
        error.contains("Job was cancelled", ignoreCase = true) ||
            error.contains("CancellationException", ignoreCase = true)
    } == true

internal fun isPhotoDependencyFailure(lastError: String?): Boolean =
    lastError?.contains("Wymagane zdjęcie nie zostało wysłane", ignoreCase = true) == true

internal fun shouldRepairPhotoUpload(status: String, lastError: String?): Boolean =
    status in setOf(
        PhotoUploadStatus.Pending.storageValue,
        PhotoUploadStatus.Uploading.storageValue,
        PhotoUploadStatus.Verifying.storageValue,
        PhotoUploadStatus.FailedRetryable.storageValue,
    ) || (status == PhotoUploadStatus.FailedPermanent.storageValue && isWorkerCancellationFailure(lastError))

internal fun shouldRepairRouteAction(status: String, lastError: String?): Boolean =
    status in setOf(
        RouteActionStatus.Pending,
        RouteActionStatus.WaitingForPhotos,
        RouteActionStatus.Syncing,
        RouteActionStatus.FailedRetryable,
    ) || (
        status == RouteActionStatus.FailedPermanent &&
            (isWorkerCancellationFailure(lastError) || isPhotoDependencyFailure(lastError))
    )
