package pl.firmatpp.kierowca.data.upload

import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import java.io.File
import pl.firmatpp.kierowca.data.ApiErrorKind
import pl.firmatpp.kierowca.data.ApiErrorMapper
import pl.firmatpp.kierowca.data.DriverRepository
import pl.firmatpp.kierowca.diagnostics.AppDiagnostics

class PhotoUploadWorker(
    appContext: Context,
    params: WorkerParameters,
) : CoroutineWorker(appContext, params) {
    private val database = DriverDatabase.get(appContext)
    private val dao = database.photoUploadDao()
    private val routeActionDao = database.routeActionDao()
    private val routeActionOutbox = RouteActionOutbox(appContext)
    private val repository = DriverRepository(appContext)

    override suspend fun doWork(): Result {
        val clientRequestId = inputData.getString(KEY_CLIENT_REQUEST_ID) ?: return Result.failure()
        val upload = dao.find(clientRequestId) ?: return Result.failure()
        val file = File(upload.localPath)

        if (!file.exists()) {
            val message = "Lokalny plik zdjęcia nie istnieje. Zdjęcie nie zostało zapisane."
            dao.updateStatus(
                clientRequestId = clientRequestId,
                status = PhotoUploadStatus.FailedPermanent.storageValue,
                progress = 0,
                lastError = PhotoUploadFailureDetails.local(message),
                attemptIncrement = 0,
            )
            return Result.failure()
        }

        dao.updateStatus(
            clientRequestId = clientRequestId,
            status = PhotoUploadStatus.Uploading.storageValue,
            progress = 10,
            lastError = null,
            attemptIncrement = 1,
        )
        setProgress(workDataOf(KEY_PROGRESS to 10))

        return runCatching {
            setProgress(workDataOf(KEY_PROGRESS to 70))
            val response = repository.uploadQueuedPhoto(upload)

            dao.updateStatus(
                clientRequestId = clientRequestId,
                status = PhotoUploadStatus.Verifying.storageValue,
                progress = 90,
                lastError = null,
                attemptIncrement = 0,
            )
            setProgress(workDataOf(KEY_PROGRESS to 90))

            val receipt = response.receipt
            if (!PhotoUploadReceiptVerifier.matches(upload, receipt)) {
                error("Serwer nie potwierdził zgodności zdjęcia. Zdjęcie nie zostało zapisane.")
            }

            dao.markConfirmed(clientRequestId, receipt.serverPhotoId)
            enqueueRouteActionsWaitingForPhoto(clientRequestId)
            Result.success()
        }.getOrElse { throwable ->
            val error = ApiErrorMapper.map(throwable)
            AppDiagnostics.reportNonFatal(
                throwable = throwable,
                operation = "photo_upload_worker",
                keys = mapOf(
                    "client_request_id" to clientRequestId,
                    "route_id" to upload.routeId,
                    "api_error_kind" to error.kind.name,
                    "api_error_code" to error.code,
                    "api_status_code" to error.statusCode,
                    "retryable" to error.retryable,
                ),
            )
            val status = if (error.retryable) PhotoUploadStatus.FailedRetryable else PhotoUploadStatus.FailedPermanent
            dao.updateStatus(
                clientRequestId = clientRequestId,
                status = status.storageValue,
                progress = 0,
                lastError = PhotoUploadFailureDetails.fromApiError(error),
                attemptIncrement = 0,
            )

            if (error.retryable && error.kind != ApiErrorKind.Auth) Result.retry() else Result.failure()
        }
    }

    private suspend fun enqueueRouteActionsWaitingForPhoto(clientRequestId: String) {
        routeActionDao.waitingForPhotosActions()
            .filter { routeActionReferencesPhotoClientRequest(it, clientRequestId) }
            .forEach { routeActionOutbox.enqueueWorker(it.clientActionId) }
    }

    companion object {
        const val KEY_CLIENT_REQUEST_ID = "clientRequestId"
        const val KEY_PROGRESS = "progress"

        fun uniqueWorkName(clientRequestId: String): String = "photo-upload-$clientRequestId"
    }
}
