package invalid.lena.rsend

import android.content.Context
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit

// Scheduler runs the sync on a self-perpetuating chain of one-time jobs: each
// run re-arms the next from SyncWorker. This sidesteps WorkManager's 15-minute
// periodic floor (so the interval can be as low as 1 minute) and tends to be
// more reliable across OEMs.
object Scheduler {

    const val NAME = "periodic-sync"

    // Floor on the chained delay so a sync that runs longer than the interval
    // cannot turn the chain into a hot loop.
    private val MIN_DELAY_MS = TimeUnit.SECONDS.toMillis(15)

    // apply (re)arms or cancels the chain to match the schedule config. force
    // decides what to do when a job is already pending under NAME:
    //   force=false (KEEP)    leave a pending countdown running; only arm when
    //                         nothing is pending. Used on app open and boot, so
    //                         returning to the app does not reset the timer.
    //   force=true  (REPLACE) cancel and re-arm now. Used when the schedule
    //                         config changes.
    fun apply(ctx: Context, force: Boolean = false) {
        val cfg = Config.load(ctx)
        val intervalMs = TimeUnit.MINUTES.toMillis(intervalMinutes(cfg))
        arm(ctx, cfg, force, intervalMs)
    }

    // armAfterRun schedules the next run so the cadence is measured from when
    // this run STARTED, not when it finished: delay = interval - runtime. That
    // keeps "every N minutes" close to N minutes instead of N plus the sync
    // duration. Always replaces, since the finishing run holds the unique name.
    fun armAfterRun(ctx: Context, runMillis: Long) {
        val cfg = Config.load(ctx)
        val intervalMs = TimeUnit.MINUTES.toMillis(intervalMinutes(cfg))
        arm(ctx, cfg, force = true, delayMs = (intervalMs - runMillis).coerceAtLeast(MIN_DELAY_MS))
    }

    private fun intervalMinutes(cfg: Config): Long =
        cfg.schedule.intervalMinutes.toLong().coerceAtLeast(1)

    private fun arm(ctx: Context, cfg: Config, force: Boolean, delayMs: Long) {
        val s = cfg.schedule
        val wm = WorkManager.getInstance(ctx)

        val ready = cfg.remote.host.isNotEmpty() && cfg.folders.isNotEmpty() &&
            Keys.exists(ctx) && Keys.pinned(ctx)
        if (!s.enabled || !ready) {
            wm.cancelUniqueWork(NAME)
            return
        }

        val constraints = Constraints.Builder()
            .setRequiredNetworkType(if (s.wifiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)
            .setRequiresCharging(s.requireCharging)
            .build()
        val req = OneTimeWorkRequestBuilder<SyncWorker>()
            .setInitialDelay(delayMs, TimeUnit.MILLISECONDS)
            .setConstraints(constraints)
            .build()
        val policy = if (force) ExistingWorkPolicy.REPLACE else ExistingWorkPolicy.KEEP
        wm.enqueueUniqueWork(NAME, policy, req)
    }
}
