package invalid.lena.rsend

import android.app.Notification
import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext

// SyncWorker runs one full sync as a foreground job: it pushes every configured
// folder with rsync and records the outcome in the log. It is used for both the
// manual "Sync now" action and the periodic schedule.
class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {

    companion object {
        const val KEY_FOLDER = "folder"
        const val KEY_INDEX = "i"
        const val KEY_TOTAL = "n"

        private val syncMutex = Mutex()
    }

    override suspend fun doWork(): Result = syncMutex.withLock {
        withContext(Dispatchers.IO) {
            val ctx = applicationContext
            val cfg = Config.load(ctx)
            if (cfg.remote.host.isEmpty() || cfg.remote.user.isEmpty() || cfg.folders.isEmpty() ||
                !Keys.exists(ctx) || !Keys.pinned(ctx)) {
                SyncLog(ctx).line("sync skipped: remote, folders, key, or pinned host missing")
                return@withContext Result.failure()
            }

            val log = SyncLog(ctx)
            log.rotateIfBig()
            log.line("sync started (${cfg.folders.size} folders)")
            // A foreground service lets long syncs survive, but Android 12+ forbids
            // starting one when the periodic job fires in the background. Try it, and
            // fall back to a plain background job when it is not allowed.
            try {
                setForeground(foregroundInfo())
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                log.line("foreground unavailable, running in background: ${e.javaClass.simpleName}: ${e.message}")
            }

            var ok = true
            val total = cfg.folders.size
            cfg.folders.forEachIndexed { i, f ->
                val label = f.name.ifEmpty { f.local }
                // Publish progress the dashboard observes (current folder, i of n).
                setProgress(workDataOf(KEY_FOLDER to label, KEY_INDEX to i + 1, KEY_TOTAL to total))
                log.line("=== $label ===")
                // Guard against a hand-edited config: an empty path would rsync /.
                val code = if (f.local.isEmpty() || f.remote.isEmpty()) {
                    log.line("error: local or remote path not set")
                    1
                } else try {
                    RsyncRunner.runFolder(ctx, cfg.remote, f, log)
                } catch (e: CancellationException) {
                    log.line("cancelled")
                    throw e
                } catch (e: Exception) {
                    log.line("error: ${e.javaClass.simpleName}: ${e.message}")
                    1
                }
                log.line("exit=$code")
                if (code != 0) ok = false
            }
            log.line("sync finished ok=$ok")
            LastSync.set(ctx, ok)
            if (!ok) notifyError(ctx)
            if (ok) Result.success() else Result.failure()
        }
    }

    private fun notifyError(ctx: Context) {
        val n = NotificationCompat.Builder(ctx, App.CHANNEL)
            .setContentTitle("rsend")
            .setContentText("Sync failed. Open the app and check the log.")
            .setSmallIcon(R.drawable.ic_notification)
            .setAutoCancel(true)
            .build()
        try {
            NotificationManagerCompat.from(ctx).notify(2, n)
        } catch (_: SecurityException) {
            // POST_NOTIFICATIONS not granted; the log still records the failure.
        }
    }

    private fun foregroundInfo(): ForegroundInfo {
        val n: Notification = NotificationCompat.Builder(applicationContext, App.CHANNEL)
            .setContentTitle("rsend")
            .setContentText("Syncing")
            .setSmallIcon(R.drawable.ic_notification)
            .setOngoing(true)
            .build()
        return if (Build.VERSION.SDK_INT >= 29) {
            ForegroundInfo(1, n, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
        } else {
            ForegroundInfo(1, n)
        }
    }
}
