package lu.rewardflow.terminal.data.sync

import android.content.Context
import android.util.Log
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.Moshi
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import lu.rewardflow.terminal.data.api.LoyaltyApi
import lu.rewardflow.terminal.data.db.dao.PendingTransactionDao
import lu.rewardflow.terminal.data.db.entity.PendingTransaction
import lu.rewardflow.terminal.data.model.EnrollRequest
import lu.rewardflow.terminal.data.model.PointsEarnRequest
import lu.rewardflow.terminal.data.model.StampRequest
import retrofit2.HttpException
import java.io.IOException

/**
 * Drains the `pending_transactions` queue against the loyalty API.
 *
 * Scheduled by [QueueRepository.scheduleSync] when:
 * - A new action is queued (immediate try)
 * - The system reports the network became available again
 *
 * Behavior per row:
 * - HTTP success → mark synced, deleted at the end of the run.
 * - [HttpException] (4xx/5xx) → permanent fail; the action's data is
 *   stale or rejected. Mark failed and stop retrying it.
 * - [IOException] → transient (lost network mid-flight). Leave row in
 *   `pending` and return `Result.retry()` so WorkManager re-runs us
 *   under the network constraint with backoff.
 *
 * Order is FIFO by `createdAt` so a stamp queued before a redeem stays
 * before a redeem in the run order — though we don't currently queue
 * redeems anyway.
 */
@HiltWorker
class SyncWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val dao: PendingTransactionDao,
    private val api: LoyaltyApi,
    private val moshi: Moshi,
) : CoroutineWorker(appContext, workerParams) {

    private val stampAdapter = moshi.adapter(StampRequest::class.java)
    private val earnAdapter = moshi.adapter(PointsEarnRequest::class.java)
    private val enrollAdapter = moshi.adapter(EnrollRequest::class.java)

    override suspend fun doWork(): Result {
        val pending = dao.getPending()
        if (pending.isEmpty()) return Result.success()

        var hadTransientFailure = false
        for (tx in pending) {
            when (process(tx)) {
                ProcessOutcome.Synced -> dao.markSynced(tx.id)
                ProcessOutcome.PermanentFailure -> {
                    // markFailed() bumps retryCount + records the error.
                    // Already handled inside process(). Move on to the next.
                }
                ProcessOutcome.Transient -> {
                    hadTransientFailure = true
                    // Stop draining — we've lost the network mid-run.
                    break
                }
            }
        }

        // Sweep synced rows out of the table so the queue size stays
        // bounded over a long-running offline session.
        runCatching { dao.deleteSynced() }

        return if (hadTransientFailure) Result.retry() else Result.success()
    }

    private suspend fun process(tx: PendingTransaction): ProcessOutcome {
        return try {
            when (tx.type) {
                TYPE_STAMP -> {
                    val req = stampAdapter.fromJson(tx.requestJson)
                    if (req == null) {
                        dao.markFailed(tx.id, "Malformed queued payload")
                        return ProcessOutcome.PermanentFailure
                    }
                    api.addStamp(req)
                }
                TYPE_POINTS_EARN -> {
                    val req = earnAdapter.fromJson(tx.requestJson)
                    if (req == null) {
                        dao.markFailed(tx.id, "Malformed queued payload")
                        return ProcessOutcome.PermanentFailure
                    }
                    api.earnPoints(req)
                }
                TYPE_ENROLL -> {
                    val req = enrollAdapter.fromJson(tx.requestJson)
                    if (req == null) {
                        dao.markFailed(tx.id, "Malformed queued payload")
                        return ProcessOutcome.PermanentFailure
                    }
                    api.enrollCustomer(req)
                }
                else -> {
                    dao.markFailed(tx.id, "Unknown type: ${tx.type}")
                    return ProcessOutcome.PermanentFailure
                }
            }
            ProcessOutcome.Synced
        } catch (e: HttpException) {
            Log.w(TAG, "HTTP ${e.code()} syncing tx ${tx.id} (${tx.type})", e)
            dao.markFailed(tx.id, "HTTP ${e.code()}: ${e.message()}")
            ProcessOutcome.PermanentFailure
        } catch (e: IOException) {
            Log.w(TAG, "Network error syncing tx ${tx.id} — will retry", e)
            ProcessOutcome.Transient
        } catch (e: Exception) {
            Log.e(TAG, "Unexpected error syncing tx ${tx.id}", e)
            dao.markFailed(tx.id, e.message ?: e::class.simpleName ?: "Unknown error")
            ProcessOutcome.PermanentFailure
        }
    }

    private enum class ProcessOutcome { Synced, PermanentFailure, Transient }

    companion object {
        const val TYPE_STAMP = "stamp"
        const val TYPE_POINTS_EARN = "points_earn"
        const val TYPE_ENROLL = "enroll"
        private const val TAG = "SyncWorker"
    }
}
