package app.closer.notifications

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import app.closer.MainActivity
import app.closer.R
import app.closer.core.navigation.AppRoute
import app.closer.domain.repository.AppSettings
import app.closer.domain.repository.SettingsRepository
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.first
import javax.inject.Inject

/**
 * Manages local and FCM-driven partner-triggered notifications.
 *
 * Responsibilities:
 *  - Enforce notification opt-outs from [SettingsRepository].
 *  - Respect quiet hours via [QuietHoursManager].
 *  - Apply rate limits via [NotificationRateLimiter].
 *  - Collapse duplicate notifications by deriving the notification ID from
 *    the notification type and couple ID hash.
 *  - Deep link each notification to the correct screen.
 *
 * Security: notification titles and bodies are static. Answer text, prompt
 * text, and other sensitive content are never included.
 */
class PartnerNotificationManager @Inject constructor(
    @ApplicationContext private val context: Context,
    private val settingsRepository: SettingsRepository,
    private val quietHoursManager: QuietHoursManager,
    private val rateLimiter: NotificationRateLimiter,
    private val activeThreadMonitor: ActiveThreadMonitor
) {

    /**
     * Shows a partner-trigger notification if the user has not opted out,
     * quiet hours are not active, and rate limits allow it.
     *
     * @param type The notification type (decides copy, channel, and deep link).
     * @param coupleId The couple ID used for duplicate collapse.
     * @param payload Optional IDs needed to build the exact deep link route.
     */
    suspend fun show(
        type: PartnerNotificationType,
        coupleId: String,
        payload: PartnerNotificationPayload = PartnerNotificationPayload()
    ) {
        if (coupleId.isBlank()) return

        val settings = settingsRepository.settings.first()

        // Don't ping for a chat message in the conversation the user is already reading live.
        if (type == PartnerNotificationType.CHAT_MESSAGE &&
            payload.conversationId != null &&
            payload.conversationId == activeThreadMonitor.activeConversationId
        ) return

        if (!isEnabled(type, settings)) return
        if (quietHoursManager.isInQuietHours(settings.quietHours)) return
        if (!rateLimiter.canSend(type.rateType)) return

        rateLimiter.record(type.rateType)

        val route = type.routeFor(payload, coupleId)
        val notificationId = collapseId(type, coupleId)
        val avatar = payload.avatarUrl?.takeIf { it.isNotBlank() }?.let { loadAvatar(it) }

        showNotification(notificationId, type, route, avatar)
    }

    /** Best-effort partner-avatar load for a richer notification; null on any failure. */
    private suspend fun loadAvatar(url: String): android.graphics.Bitmap? = runCatching {
        val loader = coil.ImageLoader(context)
        val request = coil.request.ImageRequest.Builder(context)
            .data(url)
            .allowHardware(false)
            .build()
        (loader.execute(request).drawable as? android.graphics.drawable.BitmapDrawable)?.bitmap
    }.getOrNull()

    /**
     * Maps a remote FCM message type to a [PartnerNotificationType] and shows it.
     *
     * Unknown types are ignored so the backend cannot force arbitrary notification text.
     */
    suspend fun handleRemote(
        type: String,
        coupleId: String,
        payload: PartnerNotificationPayload = PartnerNotificationPayload()
    ) {
        val notificationType = PartnerNotificationType.fromRemoteType(type) ?: return
        show(notificationType, coupleId, payload)
    }

    private fun isEnabled(type: PartnerNotificationType, settings: AppSettings): Boolean {
        return when (type) {
            PartnerNotificationType.CHAT_MESSAGE -> settings.chatMessageEnabled
            PartnerNotificationType.OUTCOME_REMINDER -> settings.outcomeReminderEnabled
            else -> when (type.rateType) {
                NotificationRateLimiter.Type.PARTNER_TRIGGER -> settings.partnerAnsweredEnabled
                NotificationRateLimiter.Type.REMINDER -> settings.dailyReminderEnabled
            }
        }
    }

    private fun showNotification(
        id: Int,
        type: PartnerNotificationType,
        route: String,
        largeIcon: android.graphics.Bitmap? = null
    ) {
        if (!NotificationManagerCompat.from(context).areNotificationsEnabled()) return

        val deepLinkUri = Uri.parse("${DEEP_LINK_SCHEME}://$DEEP_LINK_HOST/$route")
        val intent = Intent(context, MainActivity::class.java).apply {
            action = Intent.ACTION_VIEW
            data = deepLinkUri
            flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
        }
        val pendingIntent = PendingIntent.getActivity(
            context,
            id,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(context, type.channelId)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle(type.title)
            .setContentText(type.body)
            .setAutoCancel(true)
            .setContentIntent(pendingIntent)
            .setCategory(NotificationCompat.CATEGORY_SOCIAL)
            .apply { if (largeIcon != null) setLargeIcon(largeIcon) }
            .build()

        NotificationManagerCompat.from(context).notify(id, notification)
    }

    private fun collapseId(type: PartnerNotificationType, coupleId: String): Int {
        // Same type + same couple always collapses to the same ID,
        // preventing duplicate notifications in the shade.
        val hash = type.name.hashCode() * 31 + coupleId.hashCode()
        return (hash and 0x7FFFFFFF) % ID_MODULO
    }

    companion object {
        private const val DEEP_LINK_SCHEME = "closer"
        private const val DEEP_LINK_HOST = "closer.app"
        private const val ID_MODULO = 100_000
    }
}

/**
 * The supported partner-trigger notification types.
 *
 * Titles and bodies are fixed strings; no user-generated content is ever shown.
 */
enum class PartnerNotificationType(
    val title: String,
    val body: String,
    val channelId: String,
    val rateType: NotificationRateLimiter.Type
) {
    PARTNER_ANSWERED(
        title = "Your partner answered.",
        body = "Your turn to unlock the reveal.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    REVEAL_READY(
        title = "Your reveal is ready.",
        body = "Open it when you're both ready.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    PARTNER_STARTED_GAME(
        title = "Your partner started a game for the two of you.",
        body = "Tap to join them.",
        channelId = NotificationChannelSetup.CHANNEL_GAMES,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    PARTNER_COMPLETED_PART(
        title = "Your partner finished their part.",
        body = "Open yours when you're ready.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    GAME_RESULTS_READY(
        title = "Your game results are ready!",
        body = "You both finished — tap to see how you compare.",
        channelId = NotificationChannelSetup.CHANNEL_GAMES,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    CHALLENGE_WAITING(
        title = "Tonight's small challenge is waiting.",
        body = "A little shared moment is ready.",
        channelId = NotificationChannelSetup.CHANNEL_REMINDERS,
        rateType = NotificationRateLimiter.Type.REMINDER
    ),
    CAPSULE_UNLOCKED(
        title = "A memory capsule just unlocked.",
        body = "Something you sealed together is ready to open.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    GENTLE_REMINDER(
        title = "Your partner is thinking about you.",
        body = "They left tonight's question open. Answer when you're ready.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    DAILY_QUESTION_REMINDER(
        title = "Tonight's question is waiting.",
        body = "Answer together before it expires.",
        channelId = NotificationChannelSetup.CHANNEL_REMINDERS,
        rateType = NotificationRateLimiter.Type.REMINDER
    ),
    CHAT_MESSAGE(
        title = "Your partner sent a message.",
        body = "Tap to read and reply.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    OUTCOME_REMINDER(
        title = "How did that go?",
        body = "Take a moment to reflect on your last date.",
        channelId = NotificationChannelSetup.CHANNEL_REMINDERS,
        rateType = NotificationRateLimiter.Type.REMINDER
    ),
    PARTNER_JOINED(
        title = "Your partner joined!",
        body = "You're connected. Time to answer tonight's question together.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    DATE_MATCH(
        title = "It's a match!",
        body = "You both want to go on this date. Time to make it happen.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    ),
    REENGAGEMENT(
        title = "It's been a while.",
        body = "Tonight's question is a good reason to reconnect.",
        channelId = NotificationChannelSetup.CHANNEL_REMINDERS,
        rateType = NotificationRateLimiter.Type.REMINDER
    ),
    // Partner left the couple or deleted their account — the user is now unpaired.
    // Routed to Home, which surfaces the "Invite partner" CTA for the now-solo user
    // (matches the push body "Tap to create a new invite"). E-002.
    PARTNER_UNPAIRED(
        title = "You're no longer paired.",
        body = "Tap to create a new invite.",
        channelId = NotificationChannelSetup.CHANNEL_PARTNER_ACTIONS,
        rateType = NotificationRateLimiter.Type.PARTNER_TRIGGER
    );

    /**
     * Builds the deep link route for this notification type.
     */
    fun routeFor(payload: PartnerNotificationPayload, coupleId: String = ""): String = when (this) {
        PARTNER_ANSWERED -> AppRoute.DAILY_QUESTION
        REVEAL_READY -> payload.questionId?.let { AppRoute.answerReveal(it) } ?: AppRoute.ANSWER_HISTORY
        PARTNER_STARTED_GAME -> AppRoute.PLAY
        PARTNER_COMPLETED_PART -> AppRoute.PLAY
        GAME_RESULTS_READY -> AppRoute.PLAY
        CHALLENGE_WAITING -> AppRoute.CONNECTION_CHALLENGES
        CAPSULE_UNLOCKED -> AppRoute.MEMORY_LANE
        GENTLE_REMINDER -> AppRoute.DAILY_QUESTION
        DAILY_QUESTION_REMINDER -> AppRoute.DAILY_QUESTION
        // Open the actual conversation so the partner can reply in place.
        CHAT_MESSAGE -> if (payload.conversationId != null && coupleId.isNotBlank()) {
            AppRoute.conversation(coupleId, payload.conversationId)
        } else {
            AppRoute.MESSAGES
        }
        OUTCOME_REMINDER -> AppRoute.SETTINGS
        PARTNER_JOINED -> if (coupleId.isNotBlank()) AppRoute.pairingSuccess(coupleId) else AppRoute.HOME
        DATE_MATCH -> AppRoute.DATE_MATCHES
        REENGAGEMENT -> AppRoute.DAILY_QUESTION
        PARTNER_UNPAIRED -> AppRoute.HOME
    }

    companion object {
        fun fromRemoteType(type: String): PartnerNotificationType? = when (type) {
            "partner_answered" -> PARTNER_ANSWERED
            "reveal_ready" -> REVEAL_READY
            "partner_started_game" -> PARTNER_STARTED_GAME
            "partner_completed_part" -> PARTNER_COMPLETED_PART
            // Server (onGameSessionUpdate) emits this type once BOTH partners finish — the reveal
            // is ready, so this maps to the results-ready copy (not "open yours when ready").
            "partner_finished_game" -> GAME_RESULTS_READY
            "game_results_ready" -> GAME_RESULTS_READY
            // Server emits 'challenge_day_ready'; keep the legacy 'challenge_waiting' alias too.
            "challenge_day_ready", "challenge_waiting" -> CHALLENGE_WAITING
            "memory_capsule_unlocked" -> CAPSULE_UNLOCKED
            "gentle_reminder" -> GENTLE_REMINDER
            // Server emits both 'daily_question' (assignment) and 'daily_question_reminder' — both open Today.
            "daily_question", "daily_question_reminder" -> DAILY_QUESTION_REMINDER
            "chat_message" -> CHAT_MESSAGE
            "outcome_reminder" -> OUTCOME_REMINDER
            "partner_joined" -> PARTNER_JOINED
            "date_match" -> DATE_MATCH
            "reengagement" -> REENGAGEMENT
            "partner_left", "partner_deleted_account" -> PARTNER_UNPAIRED
            // NB: "invite_created" is a server-side audit-log entry (read:true, never sent
            // as a push) and "spki" is a crypto key-format string in the RevenueCat webhook
            // (not a notification type at all) — neither needs client routing. E-002.
            else -> null
        }
    }
}

/**
 * Optional IDs used to build exact deep link routes.
 *
 * No answer text, prompt text, or decrypted content is included.
 */
data class PartnerNotificationPayload(
    val questionId: String? = null,
    val conversationId: String? = null,
    val gameSessionId: String? = null,
    val capsuleId: String? = null,
    val challengeId: String? = null,
    /** Sender's avatar URL, used as the notification large icon when present. */
    val avatarUrl: String? = null
)
