package pl.firmatpp.kierowca.data.upload

import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.google.gson.Gson
import pl.firmatpp.kierowca.data.ApiErrorKind
import pl.firmatpp.kierowca.data.ApiErrorMapper
import pl.firmatpp.kierowca.data.DriverRepository
import pl.firmatpp.kierowca.data.model.FinishRouteBody
import pl.firmatpp.kierowca.data.model.RouteActionResponse
import pl.firmatpp.kierowca.data.model.StartRouteBody
import pl.firmatpp.kierowca.data.sync.DriverSyncRepository
import pl.firmatpp.kierowca.diagnostics.AppDiagnostics
import pl.firmatpp.kierowca.sync.DriverSyncWorker

class RouteActionWorker(
    appContext: Context,
    params: WorkerParameters,
) : CoroutineWorker(appContext, params) {
    private val database = DriverDatabase.get(appContext)
    private val dao = database.routeActionDao()
    private val photoDao = database.photoUploadDao()
    private val repository = DriverRepository(appContext)
    private val syncRepository = DriverSyncRepository(appContext, repository)
    private val gson = Gson()

    override suspend fun doWork(): Result {
        val clientActionId = inputData.getString(KEY_CLIENT_ACTION_ID) ?: return Result.failure()
        val action = dao.find(clientActionId) ?: return Result.failure()
        val photoClientRequestIds = photoClientRequestIds(action)

        val waitingForPhotos = photoClientRequestIds
            .mapNotNull { photoDao.find(it) }
            .any { it.status != PhotoUploadStatus.Confirmed.storageValue }

        if (waitingForPhotos) {
            dao.updateStatus(
                clientActionId = clientActionId,
                status = RouteActionStatus.WaitingForPhotos,
                lastError = "Czekam na wysłanie zdjęć etapu.",
                attemptIncrement = 0,
            )
            return Result.retry()
        }

        dao.updateStatus(
            clientActionId = clientActionId,
            status = RouteActionStatus.Syncing,
            lastError = null,
            attemptIncrement = 1,
        )

        return runCatching {
            val response = sendAction(action, photoClientRequestIds)
            dao.updateStatus(
                clientActionId = clientActionId,
                status = RouteActionStatus.Confirmed,
                lastError = null,
                attemptIncrement = 0,
            )
            syncRepository.cacheConfirmedRoute(response.route, response.route.routeDate)
            DriverSyncWorker.enqueue(applicationContext, response.route.routeDate, response.route.id)
            Result.success()
        }.getOrElse { throwable ->
            val error = ApiErrorMapper.map(throwable)
            AppDiagnostics.reportNonFatal(
                throwable = throwable,
                operation = "route_action_worker",
                keys = mapOf(
                    "client_action_id" to clientActionId,
                    "route_id" to action.routeId,
                    "route_action" to action.action,
                    "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 = when {
                error.kind == ApiErrorKind.Conflict -> RouteActionStatus.FailedConflict
                error.retryable -> RouteActionStatus.FailedRetryable
                else -> RouteActionStatus.FailedPermanent
            }
            dao.updateStatus(
                clientActionId = clientActionId,
                status = status,
                lastError = error.message,
                attemptIncrement = 0,
            )

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

    private suspend fun sendAction(action: RouteActionEntity, photoClientRequestIds: List<String>): RouteActionResponse =
        when (action.action) {
            RouteActionType.Start -> repository.startRoute(
                routeId = action.routeId,
                body = StartRouteBody(
                    clientActionId = action.clientActionId,
                    loadingWeight = action.weight,
                    occurredAt = action.occurredAt,
                    photoClientRequestIds = photoClientRequestIds,
                ),
            )
            RouteActionType.Finish -> repository.finishRoute(
                routeId = action.routeId,
                body = FinishRouteBody(
                    clientActionId = action.clientActionId,
                    unloadingWeight = action.weight,
                    occurredAt = action.occurredAt,
                    photoClientRequestIds = photoClientRequestIds,
                ),
            )
            else -> error("Nieznana akcja kursu: ${action.action}")
        }

    private fun photoClientRequestIds(action: RouteActionEntity): List<String> =
        runCatching {
            gson.fromJson(action.photoClientRequestIdsJson, Array<String>::class.java)?.toList().orEmpty()
        }.getOrDefault(emptyList())
            .map { it.trim() }
            .filter { it.isNotBlank() }
            .distinct()

    companion object {
        const val KEY_CLIENT_ACTION_ID = "clientActionId"

        fun uniqueWorkName(clientActionId: String): String = "route-action-$clientActionId"
    }
}
