package com.isidroid.job_sender.repository

import android.content.Context
import com.isidroid.job.constant.JobStatus
import com.isidroid.job.constant.SendJobStatus
import com.isidroid.job.model.PrintJob
import com.isidroid.job.repository.JobRepository
import com.isidroid.job_sender.SendJobEventCollectorFlow
import com.isidroid.job_sender.UploadPageException
import com.isidroid.job_sender.data.source.local.SendJobLocalSource
import com.isidroid.job_sender.data.source.remote.SendJobNetworkSource
import com.isidroid.job_sender.domain.model.PrintJobSender
import com.isidroid.job_sender.ext.createRenderItems
import com.isidroid.job_sender.ext.decreaseCounter
import com.isidroid.rendering.repository.RenderRepository
import com.isidroid.spot.repository.SpotRepository
import timber.log.Timber

internal class JobSendRepositoryImpl(
    private val context: Context,
    private val renderRepository: RenderRepository,
    private val spotRepository: SpotRepository,
    private val printJobRepository: JobRepository,
    private val sendJobLocalSource: SendJobLocalSource,
    private val sendJobNetworkSource: SendJobNetworkSource,
    private val eventCollector: SendJobEventCollectorFlow,
) : JobSendRepository {

    override suspend fun completeUpload(jobId: String, token: String): PrintJob? {
        val printJob = printJobRepository.readLocalList(ids = listOf(jobId)).firstOrNull()
        val uploadResult = sendJobNetworkSource.complete(jobId = jobId, token = token)

        return printJob?.copy(status = JobStatus.READY_TO_PRINT)?.also { printJobRepository.insert(it) }?.takeIf { uploadResult }
    }

    override suspend fun getJobList(status: Int?): Collection<PrintJob> = printJobRepository.readLocalList(status = status)
    override suspend fun getJobSenderList(status: Int?): Collection<PrintJobSender> = sendJobLocalSource.findByStatus(status)

    // let's check whether there is not rendered print jobs
    override suspend fun checkNotRenderedPrintJobs(items: Collection<PrintJob>): Collection<PrintJobSender>? {
        if (items.isNotEmpty()) {
            eventCollector.updateStatus(JobStatus.RENDER_STARTED, *items.map { it.id }.toTypedArray())

            val renderItems = createRenderItems(context, spotRepository, renderRepository, eventCollector, items)
            val jobIds = renderItems.map { it.printJobId }.distinct()

            printJobRepository.updateJobStatus(JobStatus.CREATED, *jobIds.toTypedArray())
            sendJobLocalSource.insert(*renderItems.toTypedArray())

            return renderItems
        }

        return null
    }

    // get all pending jobs and send them one by one
    override suspend fun readFilesAndSend(items: Collection<PrintJobSender>): Collection<PrintJob> {
        val total = items.size
        val sendJobResults = items.groupBy { it.printJobId }.mapValues { it.value.size }.toMutableMap()

        for ((index, item) in items.withIndex()) {
            eventCollector.updateProgress(index, total)
            try {
                updateStatus(item, SendJobStatus.SENDING)

                val uploadResult = sendJobNetworkSource.uploadPage(
                    jobId = item.printJobId,
                    token = item.accessToken,
                    filePath = item.sourceFile
                )

                if (!uploadResult)
                    throw UploadPageException()

                sendJobResults.decreaseCounter(item.printJobId)
                updateStatus(item, SendJobStatus.COMPLETE)

            } catch (t: Throwable) {
                updateStatus(item, SendJobStatus.RENDERED)
                Timber.e(t)
                eventCollector.jobError(jobId = item.printJobId, t = t)
            }
        }

        eventCollector.updateProgress(total, total)

        // update jobs
        val successJobIds = sendJobResults.filter { it.value == 0 }.keys
        printJobRepository.updateJobStatus(status = JobStatus.RENDER_UPLOAD, *successJobIds.toTypedArray())

        val failedJobIds = sendJobResults.filter { it.value != 0 }.keys
        printJobRepository.updateJobStatus(status = JobStatus.UPLOAD_ERROR, *failedJobIds.toTypedArray())

        val successJobs = printJobRepository.readLocalList(ids = successJobIds.toList())
        val failedJobs = printJobRepository.readLocalList(ids = failedJobIds.toList())

        // notify event emitters
        if (successJobs.isNotEmpty())
            eventCollector.updateStatus(status = JobStatus.RENDER_UPLOAD, *successJobs.map { it.id }.toTypedArray())

        if (failedJobs.isNotEmpty())
            eventCollector.updateStatus(status = JobStatus.UPLOAD_ERROR, *failedJobs.map { it.id }.toTypedArray())

        return successJobs
    }

    override suspend fun markJobUploaded(items: Collection<PrintJob>): Collection<PrintJob> {
        return items.mapNotNull { job ->
            // upload
            val updateJob = completeUpload(jobId = job.id, token = job.accessToken.orEmpty())

            // notify event emitters
            eventCollector.updateStatus(status = JobStatus.READY_TO_PRINT, updateJob?.id)
            updateJob
        }
    }

    private suspend fun updateStatus(item: PrintJobSender, @SendJobStatus status: Int) {
        sendJobLocalSource.updateStatus(item, status)

        val jobStatus = when (status) {
            SendJobStatus.RENDERED -> JobStatus.READY
            SendJobStatus.SENDING -> JobStatus.SENDING
            SendJobStatus.COMPLETE -> JobStatus.READY_TO_PRINT
            else -> return
        }

        eventCollector.updateStatus(jobStatus, item.printJobId)
    }
}

