package com.tt.honeyDue.notifications

import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.tt.honeyDue.MainActivity
import com.tt.honeyDue.R
import com.tt.honeyDue.models.TaskCompletionCreateRequest
import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.widget.WidgetUpdateManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

/**
 * BroadcastReceiver for the iOS-parity notification action buttons introduced
 * in P4 Stream O. Handles Complete / Snooze / Open for task_reminder +
 * task_overdue categories, and Accept / Decline / Open for residence_invite.
 *
 * Counterpart: `iosApp/iosApp/PushNotifications/PushNotificationManager.swift`
 * (handleNotificationAction). Categories defined in
 * `iosApp/iosApp/PushNotifications/NotificationCategories.swift`.
 *
 * There is a pre-existing [com.tt.honeyDue.NotificationActionReceiver] under
 * the root package that handles widget-era task-state transitions (mark in
 * progress, cancel, etc.). That receiver is untouched by this stream; this
 * one lives under `com.tt.honeyDue.notifications` and serves only the push
 * action buttons attached by [FcmService].
 */
class NotificationActionReceiver : BroadcastReceiver() {

    /**
     * Hook for tests. Overridden to intercept async work and force it onto a
     * synchronous dispatcher. Production stays on [Dispatchers.IO].
     */
    internal var coroutineScopeOverride: CoroutineScope? = null

    override fun onReceive(context: Context, intent: Intent) {
        val scope = coroutineScopeOverride
        if (scope != null) {
            // Test path: run synchronously on the provided scope so Robolectric
            // assertions can observe post-conditions without goAsync hangs.
            scope.launch { handleAction(context.applicationContext, intent) }
            return
        }

        val pending = goAsync()
        defaultScope.launch {
            try {
                handleAction(context.applicationContext, intent)
            } catch (t: Throwable) {
                Log.e(TAG, "Action handler crashed", t)
            } finally {
                pending.finish()
            }
        }
    }

    internal suspend fun handleAction(context: Context, intent: Intent) {
        val action = intent.action ?: run {
            Log.w(TAG, "onReceive with null action")
            return
        }

        val taskId = intent.longTaskId()
        val residenceId = intent.longResidenceId()
        val notificationId = intent.getIntExtra(NotificationActions.EXTRA_NOTIFICATION_ID, 0)
        val title = intent.getStringExtra(NotificationActions.EXTRA_TITLE)
        val body = intent.getStringExtra(NotificationActions.EXTRA_BODY)
        val type = intent.getStringExtra(NotificationActions.EXTRA_TYPE)
        val deepLink = intent.getStringExtra(NotificationActions.EXTRA_DEEP_LINK)

        Log.d(TAG, "action=$action taskId=$taskId residenceId=$residenceId")

        when (action) {
            NotificationActions.COMPLETE -> handleComplete(context, taskId, notificationId)
            NotificationActions.SNOOZE -> handleSnooze(context, taskId, title, body, type, notificationId)
            NotificationActions.OPEN -> handleOpen(context, taskId, residenceId, deepLink, notificationId)
            NotificationActions.ACCEPT_INVITE -> handleAcceptInvite(context, residenceId, notificationId)
            NotificationActions.DECLINE_INVITE -> handleDeclineInvite(context, residenceId, notificationId)
            NotificationActions.SNOOZE_FIRE -> handleSnoozeFire(context, taskId, title, body, type)
            else -> Log.w(TAG, "Unknown action: $action")
        }
    }

    // ---------------- Complete ----------------

    private suspend fun handleComplete(context: Context, taskId: Long?, notificationId: Int) {
        if (taskId == null) {
            Log.w(TAG, "COMPLETE without task_id — no-op")
            return
        }
        val request = TaskCompletionCreateRequest(
            taskId = taskId.toInt(),
            completedAt = null,
            notes = "Completed from notification",
            actualCost = null,
            rating = null,
        )
        when (val result = APILayer.createTaskCompletion(request)) {
            is ApiResult.Success -> {
                Log.d(TAG, "Task $taskId completed from notification")
                cancelNotification(context, notificationId)
                WidgetUpdateManager.forceRefresh(context)
            }
            is ApiResult.Error -> {
                // Leave the notification so the user can retry.
                Log.e(TAG, "Complete failed: ${result.message}")
            }
            else -> Log.w(TAG, "Unexpected ApiResult from createTaskCompletion")
        }
    }

    // ---------------- Snooze ----------------

    private fun handleSnooze(
        context: Context,
        taskId: Long?,
        title: String?,
        body: String?,
        type: String?,
        notificationId: Int
    ) {
        if (taskId == null) {
            Log.w(TAG, "SNOOZE without task_id — no-op")
            return
        }
        SnoozeScheduler.schedule(
            context = context,
            taskId = taskId,
            delayMs = NotificationActions.SNOOZE_DELAY_MS,
            title = title,
            body = body,
            type = type
        )
        cancelNotification(context, notificationId)
    }

    /** Fired by [AlarmManager] when a snooze elapses — rebuild + post the notification. */
    private fun handleSnoozeFire(
        context: Context,
        taskId: Long?,
        title: String?,
        body: String?,
        type: String?
    ) {
        if (taskId == null) {
            Log.w(TAG, "SNOOZE_FIRE without task_id — no-op")
            return
        }
        NotificationChannels.ensureChannels(context)
        val channelId = NotificationChannels.channelIdForType(type ?: NotificationChannels.TASK_REMINDER)

        val contentIntent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
            putExtra(FcmService.EXTRA_TASK_ID, taskId)
            putExtra(FcmService.EXTRA_TYPE, type)
        }
        val pi = PendingIntent.getActivity(
            context,
            taskId.toInt(),
            contentIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(context, channelId)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle(title ?: context.getString(R.string.app_name))
            .setContentText(body ?: "")
            .setStyle(NotificationCompat.BigTextStyle().bigText(body ?: ""))
            .setAutoCancel(true)
            .setContentIntent(pi)
            .build()

        NotificationManagerCompat.from(context).apply {
            if (areNotificationsEnabled()) {
                notify(taskId.hashCode(), notification)
            }
        }
    }

    // ---------------- Open ----------------

    private fun handleOpen(
        context: Context,
        taskId: Long?,
        residenceId: Long?,
        deepLink: String?,
        notificationId: Int
    ) {
        val intent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or
                Intent.FLAG_ACTIVITY_CLEAR_TOP or
                Intent.FLAG_ACTIVITY_SINGLE_TOP
            deepLink?.takeIf { it.isNotBlank() }?.let { data = Uri.parse(it) }
            taskId?.let { putExtra(FcmService.EXTRA_TASK_ID, it) }
            residenceId?.let { putExtra(FcmService.EXTRA_RESIDENCE_ID, it) }
        }
        context.startActivity(intent)
        cancelNotification(context, notificationId)
    }

    // ---------------- Accept / Decline invite ----------------

    private suspend fun handleAcceptInvite(context: Context, residenceId: Long?, notificationId: Int) {
        if (residenceId == null) {
            Log.w(TAG, "ACCEPT_INVITE without residence_id — no-op")
            return
        }
        when (val result = APILayer.acceptResidenceInvite(residenceId.toInt())) {
            is ApiResult.Success -> {
                Log.d(TAG, "Residence invite $residenceId accepted")
                cancelNotification(context, notificationId)
            }
            is ApiResult.Error -> {
                // Leave the notification so the user can retry from the app.
                Log.e(TAG, "Accept invite failed: ${result.message}")
            }
            else -> Log.w(TAG, "Unexpected ApiResult from acceptResidenceInvite")
        }
    }

    private suspend fun handleDeclineInvite(context: Context, residenceId: Long?, notificationId: Int) {
        if (residenceId == null) {
            Log.w(TAG, "DECLINE_INVITE without residence_id — no-op")
            return
        }
        when (val result = APILayer.declineResidenceInvite(residenceId.toInt())) {
            is ApiResult.Success -> {
                Log.d(TAG, "Residence invite $residenceId declined")
                cancelNotification(context, notificationId)
            }
            is ApiResult.Error -> {
                Log.e(TAG, "Decline invite failed: ${result.message}")
            }
            else -> Log.w(TAG, "Unexpected ApiResult from declineResidenceInvite")
        }
    }

    // ---------------- helpers ----------------

    private fun cancelNotification(context: Context, notificationId: Int) {
        if (notificationId == 0) return
        val mgr = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
        if (mgr != null) {
            mgr.cancel(notificationId)
        } else {
            NotificationManagerCompat.from(context).cancel(notificationId)
        }
    }

    companion object {
        private const val TAG = "NotificationAction"

        private val defaultScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

        /**
         * Build a PendingIntent pointing at this receiver for the given action.
         * Used by [FcmService] to attach action buttons.
         */
        fun actionPendingIntent(
            context: Context,
            action: String,
            requestCodeSeed: Int,
            extras: Map<String, Any?>
        ): PendingIntent {
            val intent = Intent(context, NotificationActionReceiver::class.java).apply {
                this.action = action
                extras.forEach { (k, v) ->
                    when (v) {
                        is Int -> putExtra(k, v)
                        is Long -> putExtra(k, v)
                        is String -> putExtra(k, v)
                        null -> { /* skip */ }
                        else -> putExtra(k, v.toString())
                    }
                }
            }
            val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            } else {
                PendingIntent.FLAG_UPDATE_CURRENT
            }
            return PendingIntent.getBroadcast(
                context,
                (action.hashCode() xor requestCodeSeed),
                intent,
                flags
            )
        }
    }
}

private fun Intent.longTaskId(): Long? {
    if (!hasExtra(NotificationActions.EXTRA_TASK_ID)) return null
    val asLong = getLongExtra(NotificationActions.EXTRA_TASK_ID, Long.MIN_VALUE)
    if (asLong != Long.MIN_VALUE) return asLong
    val asInt = getIntExtra(NotificationActions.EXTRA_TASK_ID, Int.MIN_VALUE)
    return if (asInt == Int.MIN_VALUE) null else asInt.toLong()
}

private fun Intent.longResidenceId(): Long? {
    if (!hasExtra(NotificationActions.EXTRA_RESIDENCE_ID)) return null
    val asLong = getLongExtra(NotificationActions.EXTRA_RESIDENCE_ID, Long.MIN_VALUE)
    if (asLong != Long.MIN_VALUE) return asLong
    val asInt = getIntExtra(NotificationActions.EXTRA_RESIDENCE_ID, Int.MIN_VALUE)
    return if (asInt == Int.MIN_VALUE) null else asInt.toLong()
}
