package pl.firmatpp.kierowca.data.upload

import android.content.Context
import android.net.Uri
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import java.io.File
import java.security.MessageDigest
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.flow.Flow
import pl.firmatpp.kierowca.data.PhotoUploadMetadata

class DispatchSheetUploadOutbox(
    private val context: Context,
    private val dao: DispatchSheetUploadDao = DriverDatabase.get(context).dispatchSheetUploadDao(),
    private val workManager: WorkManager = WorkManager.getInstance(context),
) {
    fun observeVisibleForWorkDate(workDate: String): Flow<List<DispatchSheetUploadEntity>> =
        dao.observeVisibleForWorkDate(workDate)

    suspend fun enqueue(
        workDate: String,
        replacePhotoId: String?,
        uri: Uri,
        source: String,
        metadata: PhotoUploadMetadata,
    ): DispatchSheetUploadEntity {
        val clientRequestId = UUID.randomUUID().toString()
        val mimeType = context.contentResolver.getType(uri) ?: "image/jpeg"
        val extension = when (mimeType) {
            "image/png" -> "png"
            "image/webp" -> "webp"
            else -> "jpg"
        }
        val uploadDir = File(context.filesDir, "dispatch-sheet-upload-outbox").apply { mkdirs() }
        val localFile = File(uploadDir, "$clientRequestId.$extension")
        val sha256 = copyAndHash(uri, localFile)
        val upload = DispatchSheetUploadEntity(
            clientRequestId = clientRequestId,
            workDate = workDate,
            replacePhotoId = replacePhotoId,
            localPath = localFile.absolutePath,
            source = source,
            takenAt = metadata.takenAt,
            latitude = metadata.latitude,
            longitude = metadata.longitude,
            locationAccuracyMeters = metadata.locationAccuracyMeters,
            mimeType = mimeType,
            size = localFile.length(),
            contentSha256 = sha256,
        )

        dao.upsert(upload)
        enqueueWorker(clientRequestId)

        return upload
    }

    suspend fun discard(clientRequestId: String) {
        dao.find(clientRequestId)?.let { upload ->
            File(upload.localPath).delete()
            dao.delete(upload.clientRequestId)
        }
    }

    private fun enqueueWorker(clientRequestId: String) {
        val request = OneTimeWorkRequestBuilder<DispatchSheetUploadWorker>()
            .setInputData(workDataOf(DispatchSheetUploadWorker.KEY_CLIENT_REQUEST_ID to clientRequestId))
            .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
            .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
            .build()

        workManager.enqueueUniqueWork(
            DispatchSheetUploadWorker.uniqueWorkName(clientRequestId),
            ExistingWorkPolicy.REPLACE,
            request,
        )
    }

    private fun copyAndHash(uri: Uri, target: File): String {
        val digest = MessageDigest.getInstance("SHA-256")
        context.contentResolver.openInputStream(uri).use { input ->
            requireNotNull(input) { "Nie można odczytać zdjęcia karty spedycyjnej." }
            target.outputStream().use { output ->
                val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
                while (true) {
                    val read = input.read(buffer)
                    if (read == -1) break
                    digest.update(buffer, 0, read)
                    output.write(buffer, 0, read)
                }
            }
        }
        return digest.digest().joinToString("") { "%02x".format(it) }
    }
}
