package com.github.nullptroma.wallenc.app.tasks

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.graphics.Color
import android.os.IBinder
import android.view.View
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.github.nullptroma.wallenc.app.R
import com.github.nullptroma.wallenc.app.navigation.WallencExternalLaunch
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.tasks.TaskForegroundItem
import com.github.nullptroma.wallenc.domain.tasks.TaskForegroundUiState
import com.github.nullptroma.wallenc.domain.tasks.TaskId
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
import com.github.nullptroma.wallenc.ui.resources.resolve
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import kotlin.math.roundToInt

/**
 * Single foreground notification (one [NotificationManager.notify] id) that accumulates all task
 * states. [orchestrator.foregroundUi] enqueues into [foregroundUiQueue] while [canPush] allows
 * (at most one pending frame until the worker finishes an iteration). Indeterminate tasks: the
 * worker reuses [lastUiState] when the queue is empty so dots advance without extra queue entries.
 * Each handled state is followed by [NOTIFICATION_QUEUE_STEP_MS] before the next step.
 */
@AndroidEntryPoint
class TaskPipelineForegroundService : Service() {

    @Inject
    lateinit var orchestrator: ITaskOrchestrator

    @Inject
    lateinit var uiStrings: UiStringResolver

    private var repeat = false
    private var canPush = true
    private var lastUiState: TaskForegroundUiState? = null

    /** Dot-animation phase per task (0…3); absent means 0 for first paint. */
    private val indeterminateDotsPhaseByTaskId = ConcurrentHashMap<TaskId, Int>()

    private val foregroundUiQueue = Channel<TaskForegroundUiState>(Channel.UNLIMITED)

    private val serviceJob = SupervisorJob()
    private val serviceScope = CoroutineScope(serviceJob + Dispatchers.Main.immediate)

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

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent?.action == WallencExternalLaunch.FOREGROUND_SERVICE_ACTION_CANCEL_ALL_TASKS &&
            ::orchestrator.isInitialized
        ) {
            orchestrator.cancelAll()
        }
        return START_STICKY
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    override fun onCreate() {
        super.onCreate()
        ensureChannel()
        startForeground(
            WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID,
            buildPlaceholderNotification(),
        )

        val nm = getSystemService(NotificationManager::class.java)

        serviceScope.launch {
            var sawVisible = false
            while (true) {
                if(repeat && !foregroundUiQueue.isEmpty || !repeat) {
                    lastUiState = foregroundUiQueue.receive()
                }
                canPush = true

                val ui = lastUiState ?: continue
                when (ui) {
                    TaskForegroundUiState.Hidden -> {
                        repeat = false
                        if (sawVisible) {
                            nm.cancel(WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID)
                            indeterminateDotsPhaseByTaskId.clear()
                            stopForeground(STOP_FOREGROUND_REMOVE)
                            stopSelf()
                            return@launch
                        }
                    }

                    is TaskForegroundUiState.Visible -> {
                        if (ui.tasks.isNotEmpty()) {
                            sawVisible = true
                            nm.notify(
                                WallencExternalLaunch.ForegroundTaskPipelineNotification.NOTIFICATION_ID,
                                buildAccumulatedNotification(ui.tasks),
                            )
                        }
                        // repeatedly receive Visible with same tasks while indeterminate dots animate
                        repeat = ui.tasks.any { it.progress?.fraction == null }
                    }
                }
                delay(NOTIFICATION_QUEUE_STEP_MS)
            }
        }

        serviceScope.launch {
            orchestrator.foregroundUi.collect { ui ->
                if(canPush || ui is TaskForegroundUiState.Hidden) {
                    foregroundUiQueue.send(ui)
                    canPush = false
                }
            }
        }
    }

    override fun onDestroy() {
        foregroundUiQueue.close()
        indeterminateDotsPhaseByTaskId.clear()
        serviceScope.cancel()
        super.onDestroy()
    }

    private fun ensureChannel() {
        val nm = getSystemService(NotificationManager::class.java)
        val channel = NotificationChannel(
            WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID,
            getString(R.string.task_notification_channel_name),
            NotificationManager.IMPORTANCE_LOW,
        )
        nm.createNotificationChannel(channel)
    }

    private fun buildPlaceholderNotification(): Notification =
        NotificationCompat.Builder(this, WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID)
            .setContentTitle(getString(R.string.task_notification_title))
            .setContentText(getString(R.string.task_notification_preparing))
            .setSmallIcon(R.drawable.ic_stat_wallenc)
            .setOngoing(true)
            .setOnlyAlertOnce(true)
            .setContentIntent(openTaskPipelinePendingIntent())
            .addAction(
                0,
                getString(R.string.task_notification_cancel),
                cancelAllTasksPendingIntent(),
            )
            .build()

    private fun buildAccumulatedNotification(tasks: List<TaskForegroundItem>): Notification {
        val sorted = tasks.sortedBy { it.taskId.uuid }
        val big = RemoteViews(packageName, R.layout.notification_wallenc_tasks_big)
        applyNotificationTemplateTextColor(big)
        bindTaskRows(big, sorted)

        val collapsedSubtext = when {
            sorted.isEmpty() ->
                getString(R.string.task_notification_preparing)

            sorted.all { it.progress?.fraction == null } ->
                getString(R.string.task_notification_indeterminate)

            else -> resources.getQuantityString(
                R.plurals.task_notification_group_subtext,
                sorted.size,
                sorted.size,
            )
        }

        return NotificationCompat.Builder(
            this,
            WallencExternalLaunch.ForegroundTaskPipelineNotification.CHANNEL_ID,
        )
            .setContentTitle(getString(R.string.task_notification_title))
            .setContentText(collapsedSubtext)
            .setSmallIcon(R.drawable.ic_stat_wallenc)
            .setOngoing(true)
            .setOnlyAlertOnce(true)
            .setContentIntent(openTaskPipelinePendingIntent())
            .setStyle(NotificationCompat.DecoratedCustomViewStyle())
            .setCustomBigContentView(big)
            .addAction(
                0,
                getString(R.string.task_notification_cancel),
                cancelAllTasksPendingIntent(),
            )
            .build()
    }

    private fun openTaskPipelinePendingIntent(): PendingIntent =
        PendingIntent.getActivity(
            this,
            WallencExternalLaunch.PendingIntentRequestCodes.NOTIFICATION_TASK_PIPELINE_OPEN_TASKS,
            WallencExternalLaunch.mainActivityViewIntentForTasks(this),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )

    private fun cancelAllTasksPendingIntent(): PendingIntent =
        PendingIntent.getService(
            this,
            WallencExternalLaunch.PendingIntentRequestCodes.NOTIFICATION_TASK_PIPELINE_CANCEL_SERVICE,
            Intent(this, TaskPipelineForegroundService::class.java).setAction(
                WallencExternalLaunch.FOREGROUND_SERVICE_ACTION_CANCEL_ALL_TASKS,
            ),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
        )

    /**
     * Uses only [android.R.attr.textColor] from [android.R.style.TextAppearance_Material_Notification_Title]
     * so line size stays from layout (13sp) while color matches system notification title text.
     */
    private fun applyNotificationTemplateTextColor(remoteViews: RemoteViews) {
        val color = notificationTemplateTextColor()
        for (i in 0 until MAX_TASK_ROWS) {
            remoteViews.setTextColor(TASK_TITLE_IDS[i], color)
            remoteViews.setTextColor(TASK_SUBTITLE_IDS[i], color)
        }
    }

    private fun notificationTemplateTextColor(): Int {
        val attrs = intArrayOf(android.R.attr.textColor)
        val a = theme.obtainStyledAttributes(
            null,
            attrs,
            0,
            android.R.style.TextAppearance_Material_Notification
        )
        val c = a.getColor(0, Color.WHITE)
        a.recycle()
        return c
    }

    private fun bindTaskRows(remoteViews: RemoteViews, sorted: List<TaskForegroundItem>) {
        val n = sorted.size
        if (n == 0) {
            for (i in 0 until MAX_TASK_ROWS) {
                hideRow(remoteViews, i)
            }
            return
        }
        if (n <= MAX_TASK_ROWS) {
            for (i in 0 until n) {
                showTaskRow(remoteViews, i, sorted[i])
            }
            for (i in n until MAX_TASK_ROWS) {
                hideRow(remoteViews, i)
            }
        } else {
            for (i in 0 until MAX_TASK_ROWS - 1) {
                showTaskRow(remoteViews, i, sorted[i])
            }
            val remaining = n - (MAX_TASK_ROWS - 1)
            showOverflowRow(remoteViews, remaining)
        }
        advanceIndeterminateDotPhasesAfterBind(sorted, n)
    }

    private fun advanceIndeterminateDotPhasesAfterBind(sorted: List<TaskForegroundItem>, n: Int) {
        if (n == 0) return
        val displayed =
            if (n <= MAX_TASK_ROWS) sorted else sorted.take(MAX_TASK_ROWS - 1)
        val presentIds = sorted.map { it.taskId }.toSet()
        indeterminateDotsPhaseByTaskId.keys.retainAll { it in presentIds }
        for (t in displayed) {
            if (t.progress?.fraction == null) {
                val id = t.taskId
                indeterminateDotsPhaseByTaskId[id] =
                    ((indeterminateDotsPhaseByTaskId[id] ?: 0) + 1) % 4
            } else {
                indeterminateDotsPhaseByTaskId.remove(t.taskId)
            }
        }
    }

    private fun hideRow(remoteViews: RemoteViews, index: Int) {
        remoteViews.setViewVisibility(TASK_ROW_IDS[index], View.GONE)
        remoteViews.setViewVisibility(TASK_LABEL_BAR_ROW_IDS[index], View.GONE)
        remoteViews.setTextViewText(TASK_SUBTITLE_IDS[index], "")
    }

    private fun showTaskRow(remoteViews: RemoteViews, index: Int, task: TaskForegroundItem) {
        remoteViews.setViewVisibility(TASK_ROW_IDS[index], View.VISIBLE)
        remoteViews.setViewVisibility(TASK_LABEL_BAR_ROW_IDS[index], View.VISIBLE)
        remoteViews.setTextViewText(TASK_TITLE_IDS[index], task.title)
        val label = task.progress?.label?.resolve(uiStrings).orEmpty()
        val fraction = task.progress?.fraction
        if (fraction != null) {
            if (label.isNotEmpty()) {
                remoteViews.setViewVisibility(TASK_SUBTITLE_IDS[index], View.VISIBLE)
                remoteViews.setTextViewText(TASK_SUBTITLE_IDS[index], label)
            } else {
                remoteViews.setViewVisibility(TASK_SUBTITLE_IDS[index], View.GONE)
                remoteViews.setTextViewText(TASK_SUBTITLE_IDS[index], "")
            }
            remoteViews.setViewVisibility(TASK_PROGRESS_IDS[index], View.VISIBLE)
            val pct = (fraction.coerceIn(0f, 1f) * 100).roundToInt()
            remoteViews.setProgressBar(TASK_PROGRESS_IDS[index], 100, pct, false)
        } else {
            remoteViews.setViewVisibility(TASK_SUBTITLE_IDS[index], View.VISIBLE)
            remoteViews.setTextViewText(
                TASK_SUBTITLE_IDS[index],
                indeterminateSubtitleWithDots(task.taskId, label),
            )
            remoteViews.setViewVisibility(TASK_PROGRESS_IDS[index], View.GONE)
        }
    }

    private fun showOverflowRow(remoteViews: RemoteViews, remainingCount: Int) {
        val overflowIndex = MAX_TASK_ROWS - 1
        remoteViews.setViewVisibility(TASK_ROW_IDS[overflowIndex], View.VISIBLE)
        remoteViews.setTextViewText(
            TASK_TITLE_IDS[overflowIndex],
            resources.getQuantityString(
                R.plurals.task_notification_more_tasks,
                remainingCount,
                remainingCount,
            ),
        )
        remoteViews.setViewVisibility(TASK_LABEL_BAR_ROW_IDS[overflowIndex], View.GONE)
        remoteViews.setTextViewText(TASK_SUBTITLE_IDS[overflowIndex], "")
    }

    private fun indeterminateSubtitleWithDots(taskId: TaskId, baseLabel: String): String {
        val phase = (indeterminateDotsPhaseByTaskId[taskId] ?: 0) % 4
        val dots = when (phase) {
            0 -> ""
            1 -> "."
            2 -> ".."
            else -> "..."
        }
        return if (baseLabel.isEmpty()) dots else baseLabel + dots
    }

    companion object {
        private const val NOTIFICATION_QUEUE_STEP_MS = 500L

        /** Must match [R.layout.notification_wallenc_tasks_big] row count. */
        private const val MAX_TASK_ROWS = 8

        private val TASK_ROW_IDS = intArrayOf(
            R.id.task_row_0,
            R.id.task_row_1,
            R.id.task_row_2,
            R.id.task_row_3,
            R.id.task_row_4,
            R.id.task_row_5,
            R.id.task_row_6,
            R.id.task_row_7,
        )

        private val TASK_TITLE_IDS = intArrayOf(
            R.id.task_title_0,
            R.id.task_title_1,
            R.id.task_title_2,
            R.id.task_title_3,
            R.id.task_title_4,
            R.id.task_title_5,
            R.id.task_title_6,
            R.id.task_title_7,
        )

        private val TASK_PROGRESS_IDS = intArrayOf(
            R.id.task_progress_0,
            R.id.task_progress_1,
            R.id.task_progress_2,
            R.id.task_progress_3,
            R.id.task_progress_4,
            R.id.task_progress_5,
            R.id.task_progress_6,
            R.id.task_progress_7,
        )

        private val TASK_SUBTITLE_IDS = intArrayOf(
            R.id.task_subtitle_0,
            R.id.task_subtitle_1,
            R.id.task_subtitle_2,
            R.id.task_subtitle_3,
            R.id.task_subtitle_4,
            R.id.task_subtitle_5,
            R.id.task_subtitle_6,
            R.id.task_subtitle_7,
        )

        private val TASK_LABEL_BAR_ROW_IDS = intArrayOf(
            R.id.task_label_bar_row_0,
            R.id.task_label_bar_row_1,
            R.id.task_label_bar_row_2,
            R.id.task_label_bar_row_3,
            R.id.task_label_bar_row_4,
            R.id.task_label_bar_row_5,
            R.id.task_label_bar_row_6,
            R.id.task_label_bar_row_7,
        )
    }
}
