/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.core.push

import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import androidx.core.app.ServiceCompat
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject

/**
 * The always-connected foreground service that IS the embedded distributor
 * (PLAN.md §11, M7 Part 2 work item 1/3). It holds [NtfyStreamClient]'s persistent
 * connection to the shard's ntfy open in the background — the price of Google-free,
 * self-contained instant delivery — and posts a notification for each tickle. It runs
 * under a low-importance ongoing notification and restarts sticky; [PushManager] starts
 * and stops it as the user opts in/out or signs out.
 */
@AndroidEntryPoint
class PushService : Service() {

    @Inject lateinit var streamClient: NtfyStreamClient
    @Inject lateinit var notifier: PushNotifier
    @Inject lateinit var prefs: PushPreferences

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private var connectionJob: Job? = null

    override fun onCreate() {
        super.onCreate()
        notifier.ensureChannels()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startAsForeground()
        if (connectionJob == null) connectionJob = scope.launch { run() }
        return START_STICKY
    }

    private fun startAsForeground() {
        val type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
        } else {
            0
        }
        ServiceCompat.startForeground(this, NOTIFICATION_ID, notifier.serviceNotification(), type)
    }

    private suspend fun run() {
        val snapshot = prefs.snapshot()
        val topic = snapshot.topic
        if (topic.isNullOrBlank() || snapshot.ntfyUrl.isNullOrBlank()) {
            // Nothing to subscribe to (should not happen — PushManager starts us only
            // once a topic exists) — stop rather than hold a dead connection open.
            stopSelf()
            return
        }
        streamClient.events(snapshot.ntfyUrl, topic).collectLatest { event ->
            if (event is NtfyStreamClient.Event.Message) notifier.notify(event.tickle)
        }
    }

    override fun onDestroy() {
        connectionJob?.cancel()
        scope.cancel()
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {
        private const val NOTIFICATION_ID = 42

        fun start(context: Context) {
            val intent = Intent(context, PushService::class.java)
            context.startForegroundService(intent)
        }

        fun stop(context: Context) {
            context.stopService(Intent(context, PushService::class.java))
        }
    }
}
