package com.marcus.meditationtimer

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.os.PowerManager
import android.os.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.time.Instant
import java.util.UUID
import kotlin.math.roundToInt

enum class Phase { Idle, Running, Paused, Done }

data class TimerState(
    val phase: Phase = Phase.Idle,
    val totalMs: Long = 0L,
    val remainingMs: Long = 0L,
)

class TimerService : Service() {

    companion object {
        private const val CHANNEL_ID = "meditation_session"
        private const val NOTIF_ID = 1
        const val ACTION_START = "start"
        const val ACTION_PAUSE = "pause"
        const val ACTION_RESUME = "resume"
        const val ACTION_STOP = "stop"
        const val EXTRA_DURATION_MS = "duration_ms"

        val state = MutableStateFlow(TimerState())

        fun start(context: Context, durationMs: Long) {
            context.startForegroundService(
                Intent(context, TimerService::class.java)
                    .setAction(ACTION_START)
                    .putExtra(EXTRA_DURATION_MS, durationMs)
            )
        }

        fun send(context: Context, action: String) {
            context.startService(Intent(context, TimerService::class.java).setAction(action))
        }
    }

    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    private var tickJob: Job? = null
    private var wakeLock: PowerManager.WakeLock? = null
    private var player: MediaPlayer? = null
    private var endAt = 0L
    private var totalMs = 0L
    private var remainingMs = 0L
    private var startedAtIso = ""
    private var pauseCount = 0

    override fun onBind(intent: Intent?) = null

    override fun onCreate() {
        super.onCreate()
        getSystemService(NotificationManager::class.java).createNotificationChannel(
            NotificationChannel(CHANNEL_ID, "Meditation session", NotificationManager.IMPORTANCE_LOW)
        )
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_START -> {
                totalMs = intent.getLongExtra(EXTRA_DURATION_MS, 10 * 60_000L)
                remainingMs = totalMs
                endAt = SystemClock.elapsedRealtime() + totalMs
                startedAtIso = Instant.now().toString()
                pauseCount = 0
                startForeground(NOTIF_ID, buildNotification(remainingMs, paused = false))
                acquireWakeLock(totalMs)
                playGong()
                startTicking()
            }
            ACTION_PAUSE -> {
                tickJob?.cancel()
                pauseCount++
                remainingMs = (endAt - SystemClock.elapsedRealtime()).coerceAtLeast(0)
                releaseWakeLock()
                state.value = TimerState(Phase.Paused, totalMs, remainingMs)
                notify(buildNotification(remainingMs, paused = true))
            }
            ACTION_RESUME -> {
                endAt = SystemClock.elapsedRealtime() + remainingMs
                acquireWakeLock(remainingMs)
                notify(buildNotification(remainingMs, paused = false))
                startTicking()
            }
            ACTION_STOP -> {
                if (tickJob?.isActive == true) {
                    remainingMs = (endAt - SystemClock.elapsedRealtime()).coerceAtLeast(0)
                }
                recordSession(completed = false)
                player?.release()
                player = null
                finish(Phase.Idle)
            }
        }
        return START_NOT_STICKY
    }

    private fun startTicking() {
        tickJob?.cancel()
        tickJob = scope.launch {
            while (true) {
                val left = endAt - SystemClock.elapsedRealtime()
                if (left <= 0L) {
                    remainingMs = 0L
                    state.value = TimerState(Phase.Done, totalMs, 0L)
                    recordSession(completed = true)
                    // keep the service (and wake lock) alive until the gong finishes
                    playGong { finish(Phase.Done) }
                    return@launch
                }
                remainingMs = left
                state.value = TimerState(Phase.Running, totalMs, left)
                delay(200L)
            }
        }
    }

    private fun finish(phase: Phase) {
        tickJob?.cancel()
        releaseWakeLock()
        state.value = TimerState(phase, totalMs, 0L)
        stopForeground(STOP_FOREGROUND_REMOVE)
        stopSelf()
    }

    private fun recordSession(completed: Boolean) {
        val elapsedMs = if (completed) totalMs else totalMs - remainingMs
        // ignore sub-30s sits (gong tests, accidental starts)
        if (elapsedMs < 30_000L) return
        SessionStore.init(applicationContext)
        val session = Session(
            id = UUID.randomUUID().toString(),
            startedAt = startedAtIso,
            endedAt = Instant.now().toString(),
            plannedMin = (totalMs / 60_000L).toInt(),
            actualMin = (elapsedMs / 60_000.0).roundToInt().coerceAtLeast(1),
            completed = completed,
            pauseCount = pauseCount,
        )
        SessionStore.record(session)
        SessionStore.lastSessionId.value = session.id
    }

    private fun playGong(onComplete: (() -> Unit)? = null) {
        player?.release()
        player = MediaPlayer.create(this, R.raw.gong)?.apply {
            setOnCompletionListener {
                it.release()
                if (player == it) player = null
                onComplete?.invoke()
            }
            start()
        }
        if (player == null) onComplete?.invoke()
    }

    private fun buildNotification(remaining: Long, paused: Boolean): Notification {
        val tapIntent = PendingIntent.getActivity(
            this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE
        )
        val builder = Notification.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_notif)
            .setContentTitle(if (paused) "Meditation paused" else "Meditation in progress")
            .setContentIntent(tapIntent)
            .setOngoing(true)
            .setOnlyAlertOnce(true)
        if (!paused) {
            builder.setWhen(System.currentTimeMillis() + remaining)
                .setShowWhen(true)
                .setUsesChronometer(true)
                .setChronometerCountDown(true)
        }
        return builder.build()
    }

    private fun notify(notification: Notification) {
        getSystemService(NotificationManager::class.java).notify(NOTIF_ID, notification)
    }

    private fun acquireWakeLock(ms: Long) {
        releaseWakeLock()
        wakeLock = (getSystemService(POWER_SERVICE) as PowerManager)
            .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MeditationTimer:session")
            .apply { acquire(ms + 20_000L) }
    }

    private fun releaseWakeLock() {
        wakeLock?.let { if (it.isHeld) it.release() }
        wakeLock = null
    }

    override fun onDestroy() {
        tickJob?.cancel()
        scope.cancel()
        releaseWakeLock()
        player?.release()
        player = null
        super.onDestroy()
    }
}
