package me.khodak.claudeusage

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.SizeF
import android.widget.RemoteViews
import me.khodak.claudeusage.data.PreferencesManager
import me.khodak.claudeusage.data.UsageData
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit

class ClaudeUsageWidget : AppWidgetProvider() {

    override fun onUpdate(context: Context, manager: AppWidgetManager, ids: IntArray) {
        ids.forEach { updateWidget(context, manager, it) }
        if (PreferencesManager(context).isLoggedIn()) {
            UsageUpdateWorker.schedulePeriodicRefresh(context)
            UsageUpdateWorker.triggerImmediateRefresh(context)
        }
    }

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        if (intent.action == ACTION_REFRESH) {
            val manager = AppWidgetManager.getInstance(context)
            val ids = manager.getAppWidgetIds(
                android.content.ComponentName(context, ClaudeUsageWidget::class.java)
            )
            isRefreshing = true
            ids.forEach { updateWidget(context, manager, it) }
            UsageUpdateWorker.triggerImmediateRefresh(context)
        }
    }

    companion object {
        const val ACTION_REFRESH = "me.khodak.claudeusage.ACTION_REFRESH"
        @Volatile internal var isRefreshing = false
        @Volatile internal var currentRotation = 0f

        /** Redraw every placed widget from the current cached data (call after a refresh). */
        fun notifyDataChanged(context: Context) {
            val manager = AppWidgetManager.getInstance(context)
            val ids = manager.getAppWidgetIds(
                android.content.ComponentName(context, ClaudeUsageWidget::class.java)
            )
            ids.forEach { updateWidget(context, manager, it) }
        }

        fun updateWidget(context: Context, manager: AppWidgetManager, widgetId: Int) {
            val prefs = PreferencesManager(context)
            val apiData = prefs.getUsageData()
            val views = responsiveViews(context, prefs, apiData, widgetId)
            manager.updateAppWidget(widgetId, views)
        }

        fun updateWidget(context: Context, manager: AppWidgetManager, widgetId: Int, data: UsageData?) {
            val prefs = PreferencesManager(context)
            val views = responsiveViews(context, prefs, data, widgetId)
            manager.updateAppWidget(widgetId, views)
        }

        private fun responsiveViews(
            context: Context, prefs: PreferencesManager, apiData: UsageData?, widgetId: Int
        ): RemoteViews {
            val refreshPi = PendingIntent.getBroadcast(
                context, widgetId,
                Intent(context, ClaudeUsageWidget::class.java).apply { action = ACTION_REFRESH },
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
            val openPi = PendingIntent.getActivity(
                context, widgetId + 1000,
                Intent(context, MainActivity::class.java),
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )

            fun attach(v: RemoteViews): RemoteViews {
                v.setOnClickPendingIntent(R.id.btn_refresh, refreshPi)
                v.setOnClickPendingIntent(R.id.widget_root, openPi)
                return v
            }

            // Full-size builder follows the user's chosen style; the compact (1-cell-tall) size
            // always uses bars, since two rings don't fit that height.
            val rings = prefs.getWidgetStyle() == PreferencesManager.STYLE_RINGS
            fun buildFull() =
                if (rings) buildRingViews(context, prefs, apiData)
                else buildViews(context, prefs, apiData)

            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                RemoteViews(mapOf(
                    SizeF(50f, 50f)  to attach(buildSmallViews(context, prefs, apiData)),
                    SizeF(50f, 120f) to attach(buildFull())
                ))
            } else {
                attach(buildFull())
            }
        }

        private fun buildSmallViews(context: Context, prefs: PreferencesManager, apiData: UsageData?): RemoteViews {
            val v = RemoteViews(context.packageName, R.layout.widget_layout_small)
            applyPeak(v, showText = false)
            if (!prefs.isLoggedIn()) {
                v.setTextViewText(R.id.tv_session_value, "—")
                v.setTextViewText(R.id.tv_session_label, "Not signed in")
                v.setTextViewText(R.id.tv_weekly_value, "—")
                v.setTextViewText(R.id.tv_weekly_label, "")
                v.setTextViewText(R.id.tv_status, "")
                v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
                v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
                return v
            }
            val hasUtilization = apiData != null && apiData.fiveHourUtilization >= 0f
            val hasApiMessages = apiData != null && apiData.effectiveRemaining >= 0 && apiData.messagesLimit > 0
            val sessionStart = prefs.getSessionStart()
            when {
                hasUtilization -> {
                    val pct = apiData!!.fiveHourUtilization.toInt()
                    v.setTextViewText(R.id.tv_session_value, "$pct%")
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
                }
                hasApiMessages -> {
                    val rem = apiData!!.effectiveRemaining
                    val lim = apiData.messagesLimit
                    v.setTextViewText(R.id.tv_session_value, "$rem/$lim")
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(apiData.progressPercent, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, formatReset(apiData.resetAtEpoch))
                }
                sessionStart > 0 -> {
                    val elapsedMs = System.currentTimeMillis() - sessionStart
                    val h = TimeUnit.MILLISECONDS.toHours(elapsedMs)
                    val m = TimeUnit.MILLISECONDS.toMinutes(elapsedMs) % 60
                    v.setTextViewText(R.id.tv_session_value, if (h > 0) "${h}h${m}m" else "${m}m")
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, "active")
                }
                else -> {
                    v.setTextViewText(R.id.tv_session_value, "—")
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, "")
                }
            }
            val hasWeekly = apiData != null && apiData.weeklyUtilization >= 0f
            if (hasWeekly) {
                val wPct = apiData!!.weeklyUtilization.toInt()
                val pace = PaceCalc.compute(apiData.weeklyUtilization, apiData.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
                v.setTextViewText(R.id.tv_weekly_value, "$wPct%")
                v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, if (pace != null) MARKER_COLOR else null))
                v.setTextViewText(R.id.tv_weekly_label, formatResetShort(apiData.weeklyResetAtEpoch))
            } else {
                val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
                v.setTextViewText(R.id.tv_weekly_value, "${weeklyDays}d")
                v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
                v.setTextViewText(R.id.tv_weekly_label, "")
            }
            val resetEpoch = apiData?.effectiveResetEpoch ?: -1L
            val status = when {
                isRefreshing -> "Refreshing…"
                apiData?.isRateLimited == true -> "Rate limited · ${formatReset(resetEpoch)}"
                else -> ""
            }
            val updatedMs = apiData?.lastUpdated ?: 0L
            v.setTextViewText(R.id.tv_status,
                if (status.isNotBlank()) status else if (updatedMs > 0) formatTime(updatedMs) else "")
            v.setInt(R.id.btn_refresh, "setColorFilter",
                if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
            v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)
            return v
        }

        private fun buildViews(context: Context, prefs: PreferencesManager, apiData: UsageData?): RemoteViews {
            val v = RemoteViews(context.packageName, R.layout.widget_layout)
            applyPeak(v, showText = true)

            // ── Not logged in ────────────────────────────────────────────────
            if (!prefs.isLoggedIn()) {
                v.setTextViewText(R.id.tv_session_value, "—")
                v.setTextViewText(R.id.tv_session_label, "Not signed in")
                v.setTextViewText(R.id.tv_weekly_value, "—")
                v.setTextViewText(R.id.tv_weekly_label, "Open app to sign in")
                v.setTextViewText(R.id.tv_status, "")
                v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
                v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
                return v
            }

            // ── 5-hour window bar ────────────────────────────────────────────
            val hasUtilization = apiData != null && apiData.fiveHourUtilization >= 0f
            val hasApiMessages = apiData != null && apiData.effectiveRemaining >= 0 && apiData.messagesLimit > 0
            val sessionStart = prefs.getSessionStart()

            when {
                hasUtilization -> {
                    val pct = apiData!!.fiveHourUtilization.toInt()
                    v.setTextViewText(R.id.tv_session_value, "$pct%")
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(pct, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
                }
                hasApiMessages -> {
                    val rem = apiData!!.effectiveRemaining
                    val lim = apiData.messagesLimit
                    v.setTextViewText(R.id.tv_session_value, "$rem / $lim")
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(apiData.progressPercent, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, formatReset(apiData.resetAtEpoch))
                }
                sessionStart > 0 -> {
                    val elapsedMs = System.currentTimeMillis() - sessionStart
                    val h = TimeUnit.MILLISECONDS.toHours(elapsedMs)
                    val m = TimeUnit.MILLISECONDS.toMinutes(elapsedMs) % 60
                    val display = if (h > 0) "${h}h ${m}m" else if (m > 0) "${m}m" else "Just started"
                    v.setTextViewText(R.id.tv_session_value, display)
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, "session active")
                }
                else -> {
                    v.setTextViewText(R.id.tv_session_value, "—")
                    v.setImageViewBitmap(R.id.bar_session, BarRenderer.render(0, null, SESSION_FILL, null))
                    v.setTextViewText(R.id.tv_session_label, "")
                }
            }

            // ── 7-day usage bar ──────────────────────────────────────────────
            val hasWeekly = apiData != null && apiData.weeklyUtilization >= 0f
            if (hasWeekly) {
                val wPct = apiData!!.weeklyUtilization.toInt()
                val pace = PaceCalc.compute(apiData.weeklyUtilization, apiData.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
                v.setTextViewText(R.id.tv_weekly_value, "$wPct%")
                v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, if (pace != null) MARKER_COLOR else null))
                v.setTextViewText(R.id.tv_weekly_label, formatResetDay(apiData.weeklyResetAtEpoch))
            } else {
                val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
                v.setTextViewText(R.id.tv_weekly_value, "$weeklyDays d")
                v.setImageViewBitmap(R.id.bar_weekly, BarRenderer.render(0, null, WEEKLY_FILL, null))
                v.setTextViewText(R.id.tv_weekly_label, "active this week")
            }

            // ── Footer ───────────────────────────────────────────────────────
            val resetEpoch = apiData?.effectiveResetEpoch ?: -1L
            val status = when {
                isRefreshing -> "Refreshing…"
                apiData?.isRateLimited == true -> "Rate limited · ${formatReset(resetEpoch)}"
                else -> ""
            }
            val updatedMs = apiData?.lastUpdated ?: 0L
            v.setTextViewText(R.id.tv_status,
                if (status.isNotBlank()) status
                else if (updatedMs > 0) formatTime(updatedMs)
                else ""
            )
            v.setInt(R.id.btn_refresh, "setColorFilter",
                if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
            v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)

            return v
        }

        /**
         * Ring style of the full widget — same data/branching as [buildViews] but each metric is a
         * circular [RingRenderer] gauge (percentage drawn in the center) instead of a bar.
         */
        private fun buildRingViews(context: Context, prefs: PreferencesManager, apiData: UsageData?): RemoteViews {
            val v = RemoteViews(context.packageName, R.layout.widget_layout_rings)
            applyPeak(v, showText = true)

            if (!prefs.isLoggedIn()) {
                v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(0, null, SESSION_FILL, null, "—", "SESSION"))
                v.setImageViewBitmap(R.id.ring_weekly, RingRenderer.render(0, null, WEEKLY_FILL, null, "—", "WEEKLY"))
                v.setTextViewText(R.id.tv_session_label, "Not signed in")
                v.setTextViewText(R.id.tv_weekly_label, "Open app to sign in")
                v.setTextViewText(R.id.tv_status, "")
                return v
            }

            // ── 5-hour session ring ──────────────────────────────────────────
            val hasUtilization = apiData != null && apiData.fiveHourUtilization >= 0f
            val hasApiMessages = apiData != null && apiData.effectiveRemaining >= 0 && apiData.messagesLimit > 0
            val sessionStart = prefs.getSessionStart()
            when {
                hasUtilization -> {
                    val pct = apiData!!.fiveHourUtilization.toInt()
                    v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(pct, null, SESSION_FILL, null, "$pct%", "SESSION"))
                    v.setTextViewText(R.id.tv_session_label, formatReset(apiData.utilizationResetAtEpoch))
                }
                hasApiMessages -> {
                    val pct = apiData!!.progressPercent
                    v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(pct, null, SESSION_FILL, null, "$pct%", "SESSION"))
                    v.setTextViewText(R.id.tv_session_label, formatReset(apiData.resetAtEpoch))
                }
                sessionStart > 0 -> {
                    v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(0, null, SESSION_FILL, null, "·", "SESSION"))
                    v.setTextViewText(R.id.tv_session_label, "session active")
                }
                else -> {
                    v.setImageViewBitmap(R.id.ring_session, RingRenderer.render(0, null, SESSION_FILL, null, "—", "SESSION"))
                    v.setTextViewText(R.id.tv_session_label, "")
                }
            }

            // ── 7-day weekly ring (with pace tick) ───────────────────────────
            val hasWeekly = apiData != null && apiData.weeklyUtilization >= 0f
            if (hasWeekly) {
                val wPct = apiData!!.weeklyUtilization.toInt()
                val pace = PaceCalc.compute(apiData.weeklyUtilization, apiData.weeklyResetAtEpoch, PaceCalc.WEEKLY_WINDOW_MS)
                v.setImageViewBitmap(
                    R.id.ring_weekly,
                    RingRenderer.render(wPct, pace?.markerPct, WEEKLY_FILL, if (pace != null) MARKER_COLOR else null, "$wPct%", "WEEKLY")
                )
                v.setTextViewText(R.id.tv_weekly_label, formatResetDay(apiData.weeklyResetAtEpoch))
            } else {
                val weeklyDays = Integer.bitCount(prefs.getWeeklyMask())
                v.setImageViewBitmap(R.id.ring_weekly, RingRenderer.render(0, null, WEEKLY_FILL, null, "${weeklyDays}d", "WEEKLY"))
                v.setTextViewText(R.id.tv_weekly_label, "active this week")
            }

            // ── Footer ───────────────────────────────────────────────────────
            val resetEpoch = apiData?.effectiveResetEpoch ?: -1L
            val status = when {
                isRefreshing -> "Refreshing…"
                apiData?.isRateLimited == true -> "Rate limited · ${formatReset(resetEpoch)}"
                else -> ""
            }
            val updatedMs = apiData?.lastUpdated ?: 0L
            v.setTextViewText(R.id.tv_status,
                if (status.isNotBlank()) status else if (updatedMs > 0) formatTime(updatedMs) else "")
            v.setInt(R.id.btn_refresh, "setColorFilter",
                if (isRefreshing) 0xFFCC785C.toInt() else 0xFF999999.toInt())
            v.setFloat(R.id.btn_refresh, "setRotation", currentRotation)
            return v
        }

        private const val SESSION_FILL = 0xFFCC785C.toInt()
        private const val WEEKLY_FILL = 0xFF7B8FCC.toInt()
        private const val MARKER_COLOR = 0xFFFFFFFF.toInt()   // single-color pace marker (weekly only)

        /** Tints the header burst icon and (optionally) the PEAK text by current peak state. */
        private fun applyPeak(v: RemoteViews, showText: Boolean) {
            val peak = PeakHours.isPeak()
            v.setInt(R.id.img_peak, "setColorFilter",
                if (peak) 0xFFCC785C.toInt() else 0xFF666666.toInt())
            if (showText) {
                v.setTextViewText(R.id.tv_peak, if (peak) "🔥 PEAK" else "")
            }
        }

        private fun formatReset(epochMs: Long): String {
            if (epochMs <= 0) return ""
            val now = System.currentTimeMillis()
            if (epochMs <= now) return "Resets soon"
            val diffH = TimeUnit.MILLISECONDS.toHours(epochMs - now)
            val timeStr = SimpleDateFormat("h:mm a", Locale.US).format(Date(epochMs))
            return when {
                diffH < 24 -> "Resets at $timeStr"
                diffH < 48 -> "Resets tomorrow $timeStr"
                else -> "Resets ${SimpleDateFormat("EEE", Locale.US).format(Date(epochMs))} $timeStr"
            }
        }

        /** Weekly reset with weekday + date ("Resets Friday, Jun 6 · 3:00 PM"), never "tomorrow". */
        private fun formatResetDay(epochMs: Long): String {
            if (epochMs <= 0) return ""
            if (epochMs <= System.currentTimeMillis()) return "Resets soon"
            val day = SimpleDateFormat("EEEE", Locale.US).format(Date(epochMs))
            val date = SimpleDateFormat("MMM d", Locale.US).format(Date(epochMs))
            val timeStr = SimpleDateFormat("h:mm a", Locale.US).format(Date(epochMs))
            return "Resets $day, $date · $timeStr"
        }

        /** Compact weekly reset for the space-tight small widget: "Fri, Jun 6". */
        private fun formatResetShort(epochMs: Long): String {
            if (epochMs <= 0) return ""
            if (epochMs <= System.currentTimeMillis()) return "soon"
            return SimpleDateFormat("EEE, MMM d", Locale.US).format(Date(epochMs))
        }

        private fun formatTime(ms: Long) =
            SimpleDateFormat("h:mm a", Locale.US).format(Date(ms))
    }
}
