package com.tomhempel.rbarometer.network

import android.content.Context
import com.tomhempel.rbarometer.LanguageManager
import com.tomhempel.rbarometer.notification.UploadReminderScheduler
import com.tomhempel.rbarometer.session.SessionProgramHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

object DatabaseUploader {

    data class UploadResult(
        val submittedQuestionnaires: Int,
        val submittedScoringReviews: Int,
        val skipped: Int,
        val errors: List<UploadError>,
        val revisionNotices: List<String> = emptyList(),
    ) {
        val submitted get() = submittedQuestionnaires + submittedScoringReviews
        val hasErrors get() = errors.isNotEmpty()
        val allFailed get() = submitted == 0 && errors.isNotEmpty()
        val needsReLogin get() = errors.any { it.isAuthError }
        /** Sync/upload ran but nothing reached the server (no pending rows or all skipped). */
        val nothingUploaded get() = submitted == 0 && !hasErrors
    }

    data class UploadError(
        val clientCode: String,
        val questionnaireId: String = "",
        val profileID: String = "",
        val profileName: String = "",
        val message: String,
        /** Non-null when the server rejected with a specific error code (e.g. NOT_FOUND, 403). */
        val serverCode: String? = null,
        val isAuthError: Boolean = false,
    )

    /**
     * Uploads all pending (completed but not yet uploaded, or re-edited after last upload)
     * questionnaires for all assigned clients. Reports results via [onResult] on the main thread.
     */
    fun uploadDatabaseWithToken(
        context: Context,
        token: String,
        languageId: String = "GERMAN",
        onProgress: ((done: Int, total: Int) -> Unit)? = null,
        onResult: (UploadResult) -> Unit,
    ) {
        CoroutineScope(Dispatchers.IO).launch {
            val allPending = SessionProgramHelper.filterUploadReadyQuestionnaires(
                context,
                com.tomhempel.rbarometer.data.CompletedQuestionnaireRepository.getAllPendingUploads(),
            )
            val pendingScoring = SessionProgramHelper.filterUploadReadyScoringReviews(
                context,
                com.tomhempel.rbarometer.data.CoachScoringReviewRepository.getAllPendingUploads(),
            )
            if (allPending.isEmpty() && pendingScoring.isEmpty()) {
                val assigned = QuestionnaireCache.getAssignedClients(context)
                if (assigned.isEmpty()) {
                    withContext(Dispatchers.Main) {
                        onResult(
                            UploadResult(
                                submittedQuestionnaires = 0,
                                submittedScoringReviews = 0,
                                skipped = 0,
                                errors = listOf(
                                    UploadError(
                                        clientCode = "",
                                        message = LanguageManager.getText(languageId, "no_profile"),
                                    ),
                                ),
                            ),
                        )
                    }
                } else {
                    withContext(Dispatchers.Main) {
                        onResult(
                            UploadResult(
                                submittedQuestionnaires = 0,
                                submittedScoringReviews = 0,
                                skipped = 0,
                                errors = emptyList(),
                            ),
                        )
                    }
                }
                return@launch
            }

            var submittedQuestionnaires = 0
            var submittedScoringReviews = 0
            var skipped = 0
            val errors = mutableListOf<UploadError>()
            val revisionNotices = mutableListOf<String>()
            val total = allPending.size + pendingScoring.size
            var processed = 0

            withContext(Dispatchers.Main) { onProgress?.invoke(0, total) }

            allPending.forEach { entry ->
                val clientCode = entry.clientCode
                    val request = runCatching {
                        QuestionnaireSubmitMapper.buildRequest(
                            context = context,
                            clientCode = clientCode,
                            questionnaireId = entry.questionnaireId
                        )
                    }.getOrNull()

                    if (request == null) {
                        skipped++
                        SyncLog.w("UPLOAD", "Skipped ${entry.questionnaireId} for ${SyncLog.clientRef(clientCode)}: buildRequest returned null")
                    } else {
                    runCatching {
                        QuestionnaireApiClient.submitQuestionnaire(token, request)
                    }.onSuccess { response ->
                        submittedQuestionnaires++
                        com.tomhempel.rbarometer.data.CompletedQuestionnaireRepository
                            .markUploaded(clientCode, entry.questionnaireId, System.currentTimeMillis())
                        if (response.legacySubmit) {
                            val submitRev = response.structureRevision
                                ?: request.structureRevision
                                ?: 1
                            val serverRev = QuestionnaireCache.getQuestionnaireList(context)
                                .find { it.id == entry.questionnaireId }
                                ?.structureRevision
                                ?: submitRev
                            revisionNotices += "Uploaded ${entry.questionnaireId} using form revision $submitRev; server is on $serverRev."
                        }
                        SyncLog.d("UPLOAD", "Submitted ${entry.questionnaireId} for ${SyncLog.clientRef(clientCode)}")
                    }.onFailure { e ->
                        val apiEx = e as? ApiException
                        val serverCode = apiEx?.code
                        val authErr = TokenStore.isAuthError(e)
                        val displayMsg = when (apiEx) {
                            null -> e.message ?: "Unknown error"
                            else -> QuestionnaireApiClient.formatErrorMessage(apiEx)
                        }
                        errors += UploadError(
                            clientCode = clientCode,
                            questionnaireId = entry.questionnaireId,
                            message = displayMsg,
                            serverCode = serverCode,
                            isAuthError = authErr,
                        )
                        SyncLog.e("UPLOAD", "Failed ${entry.questionnaireId} for ${SyncLog.clientRef(clientCode)}: ${e.message}", e)
                    }
                    }
                processed++
                withContext(Dispatchers.Main) { onProgress?.invoke(processed, total) }
            }

            pendingScoring.forEach { review ->
                runCatching {
                    QuestionnaireApiClient.submitCoachScoringReview(
                        token,
                        CoachScoringReviewRequest(
                            clientCode = review.clientCode,
                            profileID = review.profileID,
                            coachBand = review.coachBand,
                            calculatedBand = review.calculatedBand,
                            weightedTotal = review.weightedTotal,
                        ),
                    )
                }.onSuccess {
                    submittedScoringReviews++
                    com.tomhempel.rbarometer.data.CoachScoringReviewRepository.markUploaded(
                        review.clientCode,
                        review.profileID,
                        System.currentTimeMillis(),
                    )
                    SyncLog.d("UPLOAD", "Submitted scoring review ${review.profileID} for ${SyncLog.clientRef(review.clientCode)}")
                }.onFailure { e ->
                    val apiEx = e as? ApiException
                    val displayMsg = when (apiEx) {
                        null -> e.message ?: "Unknown error"
                        else -> QuestionnaireApiClient.formatErrorMessage(apiEx)
                    }
                    errors += UploadError(
                        clientCode = review.clientCode,
                        profileID = review.profileID,
                        profileName = review.profileName,
                        message = displayMsg,
                        serverCode = apiEx?.code,
                        isAuthError = TokenStore.isAuthError(e),
                    )
                    SyncLog.e("UPLOAD", "Failed scoring ${review.profileID} for ${SyncLog.clientRef(review.clientCode)}: ${e.message}", e)
                }
                processed++
                withContext(Dispatchers.Main) { onProgress?.invoke(processed, total) }
            }

            if (!errors.any() && (submittedQuestionnaires + submittedScoringReviews) > 0) {
                val stillPending = UploadReminderScheduler.hasPendingUploads(context)
                if (!stillPending) {
                    UploadReminderScheduler.cancel(context)
                }
            }

            withContext(Dispatchers.Main) {
                onResult(
                    UploadResult(
                        submittedQuestionnaires = submittedQuestionnaires,
                        submittedScoringReviews = submittedScoringReviews,
                        skipped = skipped,
                        errors = errors,
                        revisionNotices = revisionNotices,
                    ),
                )
            }

            // Refresh cache in the background; opening screen runs a visible sync after the user closes upload.
            runCatching {
                SyncCoordinator.refreshManual(context, token)
            }.onFailure { e ->
                SyncLog.w("UPLOAD", "Post-upload sync failed: ${e.message}")
            }
        }
    }
}
