package dev.privacyllc.period.core.notifications

import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import dev.privacyllc.period.core.datastore.NotificationPrivacy

/**
 * Posts a reminder, applying §28's privacy modes to the actual Android APIs.
 *
 * The copy is decided by [NotificationCopy]; this file's only job is to put the
 * two versions in the right places, and getting that wrong is how a discreet
 * app leaks.
 *
 * ## The three ways this leaks if you are careless
 *
 * 1. **A private notification with no public version.** Android does not blank
 *    it — it falls back to showing the private content. `setPublicVersion` is
 *    not optional, which is why [NotificationText] has no nullable title.
 * 2. **High importance.** A heads-up notification draws its content over
 *    whatever is on screen, lock or no lock. Importance is therefore capped
 *    below heads-up unless the user chose DIRECT.
 * 3. **The channel name.** It shows in system settings and in some launchers.
 *    "Reminders" says nothing; "Period reminders" says everything, to anyone who
 *    opens the notification settings on a shared phone.
 */
class PeriodNotifier(private val context: Context) {

    /**
     * One channel per privacy mode, and that is not tidiness.
     *
     * **Android will not change a channel's importance or lock-screen
     * visibility after it has been created.** A single channel therefore keeps
     * whatever settings it had on the day it was made, so a user switching from
     * Direct to Maximum privacy would keep getting the old behaviour forever —
     * the setting would appear to work and change nothing.
     *
     * Found by an instrumented test that created the channel in one mode and
     * asserted the next mode's visibility; the second assertion failed against
     * a channel that had quietly kept the first mode's settings.
     *
     * Separate channels also give the user something better: three switches in
     * system settings they can mute independently.
     */
    fun ensureChannel(privacy: NotificationPrivacy) {
        val manager = context.getSystemService(NotificationManager::class.java) ?: return

        val channel = NotificationChannel(
            channelId(privacy),
            channelName(privacy),
            // DEFAULT never pops over the screen. Only DIRECT, which the user
            // chose explicitly, gets the more insistent behaviour.
            if (privacy == NotificationPrivacy.DIRECT) {
                NotificationManager.IMPORTANCE_HIGH
            } else {
                NotificationManager.IMPORTANCE_DEFAULT
            },
        ).apply {
            description = "Reminders you have turned on."
            lockscreenVisibility = when (privacy) {
                NotificationPrivacy.DIRECT -> Notification.VISIBILITY_PUBLIC
                else -> Notification.VISIBILITY_PRIVATE
            }
            setShowBadge(false)
        }
        manager.createNotificationChannel(channel)
    }

    /**
     * Post [text]. Returns false when the permission is absent — silently doing
     * nothing would look identical to a scheduling bug.
     */
    fun notify(
        text: NotificationText,
        privacy: NotificationPrivacy,
        contentIntent: android.app.PendingIntent?,
        actions: List<NotificationCompat.Action> = emptyList(),
    ): Boolean {
        // Checked inline rather than through hasPermission(), so lint can see
        // it. A helper method is equivalent to a human reader and invisible to
        // the analyser, and MissingPermission is one of the few lint errors
        // worth taking literally: getting it wrong is a silent no-op in
        // production, which looks exactly like a scheduling bug.
        if (android.os.Build.VERSION.SDK_INT >= 33 &&
            ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) !=
            PackageManager.PERMISSION_GRANTED
        ) {
            return false
        }
        ensureChannel(privacy)

        // The public version is a whole second notification, and it is what a
        // locked screen renders. Built first so it cannot be forgotten.
        val public = NotificationCompat.Builder(context, channelId(privacy))
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentTitle(text.publicTitle)
            .apply { text.publicBody?.let { setContentText(it) } }
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .build()

        val notification = NotificationCompat.Builder(context, channelId(privacy))
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentTitle(text.privateTitle)
            .setContentText(text.privateBody)
            .setStyle(NotificationCompat.BigTextStyle().bigText(text.privateBody))
            .setAutoCancel(true)
            .setVisibility(
                when (text.visibility) {
                    LockScreenVisibility.PUBLIC -> NotificationCompat.VISIBILITY_PUBLIC
                    LockScreenVisibility.PRIVATE -> NotificationCompat.VISIBILITY_PRIVATE
                },
            )
            .setPublicVersion(public)
            .apply {
                contentIntent?.let { setContentIntent(it) }
                actions.forEach { addAction(it) }
            }
            .build()

        NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
        return true
    }

    fun hasPermission(): Boolean =
        android.os.Build.VERSION.SDK_INT < 33 ||
            ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) ==
            PackageManager.PERMISSION_GRANTED

    companion object {
        /**
         * Channel ids, and the names beside them.
         *
         * Every name is deliberately dull. These strings appear in
         * Settings > Apps > Notifications, on a phone somebody else may be
         * holding — "Period reminders" would say everything the rest of this
         * class works to keep quiet.
         */
        fun channelId(privacy: NotificationPrivacy): String = when (privacy) {
            NotificationPrivacy.DISCREET -> "reminders_discreet"
            NotificationPrivacy.MAXIMUM_PRIVACY -> "reminders_minimal"
            NotificationPrivacy.DIRECT -> "reminders_direct"
        }

        fun channelName(privacy: NotificationPrivacy): String = when (privacy) {
            NotificationPrivacy.DISCREET -> "Reminders"
            NotificationPrivacy.MAXIMUM_PRIVACY -> "Reminders (minimal)"
            NotificationPrivacy.DIRECT -> "Reminders (detailed)"
        }

        /** One id: a reminder replaces the previous one rather than stacking. */
        const val NOTIFICATION_ID = 1001

        /** Actions come back through the app, so the tap lands on the check-in screen. */
        const val ACTION_STARTED = "dev.privacyllc.period.action.STARTED"
        const val ACTION_NOT_YET = "dev.privacyllc.period.action.NOT_YET"
        const val EXTRA_FROM_NOTIFICATION = "from_notification"

        @Suppress("UNUSED_PARAMETER")
        fun launchIntent(context: Context, action: String? = null): Intent =
            Intent().apply {
                setClassName(context, "dev.privacyllc.period.MainActivity")
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                putExtra(EXTRA_FROM_NOTIFICATION, true)
                action?.let { putExtra("reminder_action", it) }
            }
    }
}
