package com.doctate.watch.sync

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
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 android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import com.doctate.watch.DoctateApp
import com.doctate.watch.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch

/**
 * Foreground sync service. Runs the [SyncWorkerLoop] for the lifetime of
 * the process; survives Doze via `foregroundServiceType="dataSync"`.
 *
 * Lifecycle: started either from [SyncServiceLauncher.kick] (recording
 * just queued an upload) or from [DoctateApp.onCreate] when the pending
 * queue is non-empty at app launch (recovery after restart).
 *
 * Notification is intentionally low-importance and silent — the doctor
 * shouldn't be alerted every time a case syncs in the background. The
 * channel is registered once on first run.
 */
class SyncService : Service() {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private var loopJob: Job? = null
    private val kickChannel = Channel<SyncKick>(Channel.UNLIMITED)

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

    override fun onCreate() {
        super.onCreate()
        registerNotificationChannel()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildNotification()
        ServiceCompat.startForeground(
            this,
            NOTIFICATION_ID,
            notification,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
            } else 0,
        )

        // Forward the incoming kick (if any) to the loop. Multiple onStartCommand
        // calls just enqueue more kicks — the loop coalesces.
        intent?.let { i ->
            val kindOrdinal = i.getIntExtra(EXTRA_KICK_KIND, KICK_KIND_NEW_PENDING)
            val kick = when (kindOrdinal) {
                KICK_KIND_BURST_POLL -> {
                    val caseId = i.getStringExtra(EXTRA_BURST_CASE_ID).orEmpty()
                    val deadline = i.getLongExtra(EXTRA_BURST_DEADLINE_MS, 0L)
                    SyncKick.BurstPoll(caseId, deadline)
                }
                else -> SyncKick.NewPending
            }
            kickChannel.trySend(kick)
        }

        if (loopJob?.isActive != true) {
            loopJob = startLoop()
        }
        return START_STICKY
    }

    private fun startLoop(): Job {
        val app = applicationContext as DoctateApp
        val loop = SyncWorkerLoop(
            pendingStore = app.pendingStore,
            uploader = SyncWorkerLoop.Uploader { caseId, recordedAt, audio ->
                app.uploadClient.upload(caseId, recordedAt, audio)
            },
            caseStore = app.caseStore,
            state = app.syncStateProvider,
            kickChannel = kickChannel,
            poller = app.onelinersClient,
            snapshotCache = app.snapshotCache,
        )
        return scope.launch {
            try {
                loop.run()
            } catch (e: Throwable) {
                Log.w(TAG, "loop crashed: ${e.message}", e)
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        loopJob?.cancel()
        scope.cancel()
        kickChannel.close()
    }

    private fun registerNotificationChannel() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
        val nm = getSystemService(NotificationManager::class.java) ?: return
        if (nm.getNotificationChannel(CHANNEL_ID) != null) return
        val channel = NotificationChannel(
            CHANNEL_ID,
            "Doctate sync",
            NotificationManager.IMPORTANCE_LOW,
        ).apply {
            description = "Background uploads of recordings"
            setShowBadge(false)
        }
        nm.createNotificationChannel(channel)
    }

    private fun buildNotification(): Notification =
        NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Doctate")
            .setContentText("Synchronisiere Aufnahmen")
            .setSmallIcon(R.drawable.ic_complication)
            .setOngoing(true)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .build()

    companion object {
        private const val TAG = "SyncService"
        private const val NOTIFICATION_ID = 1
        private const val CHANNEL_ID = "doctate_sync"

        const val ACTION_KICK = "com.doctate.watch.sync.KICK"
        const val EXTRA_KICK_KIND = "kick_kind"
        const val EXTRA_BURST_CASE_ID = "burst_case_id"
        const val EXTRA_BURST_DEADLINE_MS = "burst_deadline_ms"

        const val KICK_KIND_NEW_PENDING = 0
        const val KICK_KIND_BURST_POLL = 1

        fun startIntent(context: Context, kick: SyncKick): Intent {
            val intent = Intent(context, SyncService::class.java).apply {
                action = ACTION_KICK
                when (kick) {
                    SyncKick.NewPending -> putExtra(EXTRA_KICK_KIND, KICK_KIND_NEW_PENDING)
                    is SyncKick.BurstPoll -> {
                        putExtra(EXTRA_KICK_KIND, KICK_KIND_BURST_POLL)
                        putExtra(EXTRA_BURST_CASE_ID, kick.caseId)
                        putExtra(EXTRA_BURST_DEADLINE_MS, kick.deadlineMs)
                    }
                }
            }
            return intent
        }
    }
}

private fun CoroutineScope.cancel() {
    (coroutineContext[Job] ?: return).cancel()
}
