package app.closer.notifications

import android.content.Context
import android.content.SharedPreferences
import java.util.Calendar
import java.util.concurrent.TimeUnit

/**
 * Persisted rate limiter for partner-trigger and reminder notifications.
 *
 * Limits (sized for a couples app where partner activity is the core loop — the old 2/day +
 * 4/week caps suppressed legitimate game start/finish and partner-action notifications after a
 * single game; these are anti-runaway ceilings, not gentle-nudge throttles):
 *  - 20 partner-trigger notifications per day
 *  - 1 reminder notification per day (proactive nudges stay gentle)
 *  - 100 total notifications per week
 *
 * Note: chat messages are NOT throttled here — foreground messages show the in-app bubble and
 * backgrounded ones are displayed by the OS from the FCM notification block, both bypassing this.
 *
 * Counts are stored in [SharedPreferences] and reset when a new day or week starts.
 */
class NotificationRateLimiter(context: Context) {

    private val prefs: SharedPreferences =
        context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

    enum class Type { PARTNER_TRIGGER, REMINDER }

    companion object {
        private const val PREFS_NAME = "notification_rate_limits"
        private const val KEY_DAY_START = "day_start"
        private const val KEY_WEEK_START = "week_start"
        private const val KEY_PARTNER_COUNT = "partner_count"
        private const val KEY_REMINDER_COUNT = "reminder_count"
        private const val KEY_TOTAL_COUNT = "total_count"

        const val MAX_PARTNER_PER_DAY = 20
        const val MAX_REMINDER_PER_DAY = 1
        const val MAX_TOTAL_PER_WEEK = 100
    }

    /**
     * Returns true if a notification of [type] can be shown right now without
     * breaking daily or weekly limits.
     *
     * Calling this method may reset day/week counters if a new window has started.
     */
    fun canSend(type: Type): Boolean {
        resetIfNewWindows()
        if (isTotalOverLimit()) return false

        return when (type) {
            Type.PARTNER_TRIGGER -> prefs.getInt(KEY_PARTNER_COUNT, 0) < MAX_PARTNER_PER_DAY
            Type.REMINDER -> prefs.getInt(KEY_REMINDER_COUNT, 0) < MAX_REMINDER_PER_DAY
        }
    }

    /**
     * Records that a notification of [type] was shown.
     *
     * Must only be called after [canSend] returned true.
     */
    fun record(type: Type) {
        resetIfNewWindows()
        prefs.edit().apply {
            putInt(KEY_TOTAL_COUNT, prefs.getInt(KEY_TOTAL_COUNT, 0) + 1)
            when (type) {
                Type.PARTNER_TRIGGER -> putInt(
                    KEY_PARTNER_COUNT,
                    prefs.getInt(KEY_PARTNER_COUNT, 0) + 1
                )
                Type.REMINDER -> putInt(
                    KEY_REMINDER_COUNT,
                    prefs.getInt(KEY_REMINDER_COUNT, 0) + 1
                )
            }
        }.apply()
    }

    /** Visible for testing. Returns the current persisted count for [type]. */
    internal fun count(type: Type): Int {
        resetIfNewWindows()
        return when (type) {
            Type.PARTNER_TRIGGER -> prefs.getInt(KEY_PARTNER_COUNT, 0)
            Type.REMINDER -> prefs.getInt(KEY_REMINDER_COUNT, 0)
        }
    }

    /** Visible for testing. Returns the current persisted weekly total. */
    internal fun totalCount(): Int {
        resetIfNewWindows()
        return prefs.getInt(KEY_TOTAL_COUNT, 0)
    }

    private fun isTotalOverLimit(): Boolean =
        prefs.getInt(KEY_TOTAL_COUNT, 0) >= MAX_TOTAL_PER_WEEK

    private fun resetIfNewWindows() {
        val now = Calendar.getInstance()
        val currentDayStart = dayStartMillis(now)
        val currentWeekStart = weekStartMillis(now)
        val lastDayStart = prefs.getLong(KEY_DAY_START, 0)
        val lastWeekStart = prefs.getLong(KEY_WEEK_START, 0)

        val editor = prefs.edit()
        if (currentDayStart != lastDayStart) {
            editor.putLong(KEY_DAY_START, currentDayStart)
                .putInt(KEY_PARTNER_COUNT, 0)
                .putInt(KEY_REMINDER_COUNT, 0)
        }
        if (currentWeekStart != lastWeekStart) {
            editor.putLong(KEY_WEEK_START, currentWeekStart)
                .putInt(KEY_TOTAL_COUNT, 0)
        }
        editor.apply()
    }

    private fun dayStartMillis(calendar: Calendar): Long {
        return calendar.timeInMillis / TimeUnit.DAYS.toMillis(1)
    }

    private fun weekStartMillis(calendar: Calendar): Long {
        // Calendar.DAY_OF_WEEK: SUNDAY=1 ... SATURDAY=7.
        val daysSinceSunday = (calendar.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY).toLong()
        val dayIndex = calendar.timeInMillis / TimeUnit.DAYS.toMillis(1)
        return dayIndex - daysSinceSunday
    }
}
