package com.agentui.desktop

import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import java.util.concurrent.TimeUnit

class TerminalListWidgetService : RemoteViewsService() {
    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        return TerminalListFactory(applicationContext)
    }
}

data class TerminalItem(
    val ephemeralSessionId: String,
    val agent: String,
    val label: String,
    val status: String,
    val alive: Boolean,
    val hookBadges: String,
    val lastUserPrompt: String,
    val statusColor: Int,
    val terminalIndex: Int // 1-based, maps to /transcript-debug/:terminalIndex
)

class TerminalListFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory {

    companion object {
        private const val TAG = "WidgetListFactory"

        // Refresh button states
        private const val ICON_NORMAL = "\u21BB"   // ↻
        private const val ICON_LOADING = "\u23F3"  // ⏳
        private const val ICON_OK = "\u2713"       // ✓
        private const val ICON_ERROR = "\u26A0"    // ⚠

        private const val COLOR_NORMAL = 0xAAFFFFFF.toInt()
        private const val COLOR_LOADING = 0xFF60a5fa.toInt()
        private const val COLOR_OK = 0xFF4ade80.toInt()
        private const val COLOR_ERROR = 0xFFf87171.toInt()

        // Terminal-style status labels shown after agent name
        private val STATUS_LABEL = mapOf(
            "idle" to "idle",
            "thinking" to "thinking...",
            "reading" to "reading",
            "writing" to "writing",
            "toolUse" to "exec",
            "permissionRequest" to "await",
            "interrupted" to "SIGINT",
            "error" to "ERR",
            "sessionStart" to "init",
            "sessionEnd" to "exit",
            "closed" to "exit 0"
        )

        private const val MAX_RETRIES = 3
        private const val RETRY_DELAY_MS = 1500L

        private val client = OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .readTimeout(5, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .build()

        private val STATUS_COLORS = mapOf(
            "idle" to 0xFF6b7280.toInt(),
            "thinking" to 0xFF60a5fa.toInt(),
            "reading" to 0xFF22d3ee.toInt(),
            "writing" to 0xFF4ade80.toInt(),
            "toolUse" to 0xFFfbbf24.toInt(),
            "permissionRequest" to 0xFFfb923c.toInt(),
            "interrupted" to 0xFFf87171.toInt(),
            "error" to 0xFFf87171.toInt(),
            "sessionStart" to 0xFF60a5fa.toInt(),
            "sessionEnd" to 0xFF6b7280.toInt()
        )

        private val TOOL_EVENTS = setOf("PreToolUse", "PostToolUse", "PostToolUseFailure")
        private val PERM_EVENTS = setOf("PermissionRequest")
        private val SESSION_EVENTS = setOf("SessionStart", "UserPromptSubmit", "Stop", "SessionEnd")
    }

    private var items = listOf<TerminalItem>()
    private val mainHandler = Handler(Looper.getMainLooper())
    private var pendingReset: Runnable? = null

    override fun onCreate() {}

    override fun onDataSetChanged() {
        // Cancel any pending reset from a previous refresh cycle
        pendingReset?.let { mainHandler.removeCallbacks(it) }
        pendingReset = null

        setRefreshButton(ICON_LOADING, COLOR_LOADING)

        val result = fetchTerminals()

        if (result == null) {
            // Network error — keep previous items, show error on button
            setRefreshButton(ICON_ERROR, COLOR_ERROR)
            scheduleResetButton(3000)
        } else {
            items = result
            // Brief success flash, then back to normal
            setRefreshButton(ICON_OK, COLOR_OK)
            scheduleResetButton(1500)
        }
    }

    override fun onDestroy() {
        items = emptyList()
    }

    override fun getCount(): Int = items.size

    override fun getViewAt(position: Int): RemoteViews {
        val views = RemoteViews(context.packageName, R.layout.widget_terminal_item)

        if (position >= items.size) return views
        val item = items[position]

        // Status dot color
        views.setTextColor(R.id.item_dot, item.statusColor)

        // Terminal-style: "$ agent [status]"
        val statusLabel = if (item.alive) item.status else "closed"
        val termLabel = STATUS_LABEL[statusLabel] ?: statusLabel
        views.setTextViewText(
            R.id.item_name,
            "$ ${item.agent} [$termLabel]"
        )

        // Badges (compact)
        views.setTextViewText(R.id.item_badges, item.hookBadges)

        // Combine label + lastUserPrompt to show maximum content
        val parts = mutableListOf<String>()
        if (item.label.isNotEmpty()) parts.add("> ${item.label}")
        if (item.lastUserPrompt.isNotEmpty()) parts.add("$ ${item.lastUserPrompt}")
        views.setTextViewText(R.id.item_label, parts.joinToString("\n"))

        val fillIntent = Intent().apply {
            putExtra("terminalIndex", item.terminalIndex)
            putExtra("agent", item.agent)
        }
        views.setOnClickFillInIntent(R.id.item_root, fillIntent)

        return views
    }

    override fun getLoadingView(): RemoteViews? = null
    override fun getViewTypeCount(): Int = 1
    override fun getItemId(position: Int): Long = position.toLong()
    override fun hasStableIds(): Boolean = false

    // ── Refresh button state management ──

    /**
     * Update just the refresh button via partiallyUpdateAppWidget.
     * This doesn't reset the ListView adapter.
     */
    private fun setRefreshButton(icon: String, color: Int) {
        try {
            val views = RemoteViews(context.packageName, R.layout.widget_transcript)
            views.setTextViewText(R.id.btn_refresh, icon)
            views.setTextColor(R.id.btn_refresh, color)

            // Update status dot indicator
            val statusDot = when (color) {
                COLOR_LOADING -> "◌"
                COLOR_OK      -> "●"
                COLOR_ERROR   -> "●"
                else          -> "●"
            }
            views.setTextViewText(R.id.widget_status_bar, statusDot)
            views.setTextColor(R.id.widget_status_bar, color)

            val mgr = AppWidgetManager.getInstance(context)
            val ids = mgr.getAppWidgetIds(
                ComponentName(context, TranscriptWidgetProvider::class.java)
            )
            mgr.partiallyUpdateAppWidget(ids, views)
        } catch (e: Exception) {
            Log.w(TAG, "Failed to update refresh button", e)
        }
    }

    /**
     * Schedule resetting the button back to normal after a delay.
     * Cancels any pending reset to prevent flickering from overlapping refreshes.
     */
    private fun scheduleResetButton(delayMs: Long) {
        pendingReset?.let { mainHandler.removeCallbacks(it) }
        val runnable = Runnable { setRefreshButton(ICON_NORMAL, COLOR_NORMAL) }
        pendingReset = runnable
        mainHandler.postDelayed(runnable, delayMs)
    }

    // ── Data fetching ──

    /**
     * Returns list on success, null on error.
     * Retries up to MAX_RETRIES on transient failures.
     * Items keep the same order as the terminal registry (T1, T2, T3...)
     * so the index maps directly to /transcript-debug/:terminalIndex
     */
    private fun fetchTerminals(): List<TerminalItem>? {
        val apiBase = ServerConfig.apiBaseUrl(context)
        if (apiBase == null) {
            Log.w(TAG, "No server URL configured")
            return null  // Show error, not empty success
        }

        var lastException: Exception? = null

        for (attempt in 1..MAX_RETRIES) {
            try {
                val result = doFetch(apiBase)
                if (result != null) return result
                // null = HTTP error, retry
                Log.w(TAG, "Fetch attempt $attempt: HTTP error, retrying...")
            } catch (e: Exception) {
                lastException = e
                Log.w(TAG, "Fetch attempt $attempt failed: ${e.message}")
            }

            if (attempt < MAX_RETRIES) {
                try { Thread.sleep(RETRY_DELAY_MS) } catch (_: InterruptedException) { break }
            }
        }

        Log.w(TAG, "All $MAX_RETRIES fetch attempts failed", lastException)
        return null
    }

    /**
     * Single fetch attempt. Returns list on success, null on HTTP error.
     * Throws on network/parse errors. Always closes the response body.
     */
    private fun doFetch(apiBase: String): List<TerminalItem>? {
        val url = "$apiBase/session-state"
        val req = Request.Builder().url(url).build()
        val resp = client.newCall(req).execute()

        return resp.use { response ->
            if (!response.isSuccessful) {
                Log.w(TAG, "HTTP ${response.code} from $url")
                return@use null
            }

            val body = response.body?.string() ?: "{}"
            val json = JSONObject(body)
            val registry = json.optJSONArray("registry")
            val agents = json.optJSONObject("agents")

            if (registry == null || registry.length() == 0) return@use emptyList()

            val result = mutableListOf<TerminalItem>()

            for (i in 0 until registry.length()) {
                val entry = registry.getJSONObject(i)
                val agentName = entry.optString("agent", "")
                val ephId = entry.optString("ephemeralSessionId", "")
                val label = entry.optString("label", "")
                val alive = entry.optBoolean("alive", false)
                val terminalIndex = i + 1

                val agentState = agents?.optJSONObject(agentName)

                val status = agentState?.optString("status", if (alive) "idle" else "closed")
                    ?: if (alive) "idle" else "closed"
                val statusColor = STATUS_COLORS[status] ?: STATUS_COLORS["idle"]!!

                val lastUserPrompt = if (agentState != null) extractLastUserPrompt(agentState) else ""
                val hookBadges = if (agentState != null) buildBadgeString(agentState) else ""

                result.add(
                    TerminalItem(
                        ephemeralSessionId = ephId,
                        agent = agentName,
                        label = label,
                        status = status,
                        alive = alive,
                        hookBadges = hookBadges,
                        lastUserPrompt = lastUserPrompt,
                        statusColor = statusColor,
                        terminalIndex = terminalIndex
                    )
                )
            }

            result
        }
    }

    private fun extractLastUserPrompt(state: JSONObject): String {
        val history = state.optJSONArray("hookHistory") ?: return ""
        for (i in history.length() - 1 downTo 0) {
            val entry = history.optJSONObject(i) ?: continue
            if (entry.optString("event") == "UserPromptSubmit") {
                val detail = entry.optString("detail", "")
                if (detail.isNotEmpty()) return detail.take(200)
            }
        }
        val stopResp = state.optString("lastStopResponse", "")
        if (stopResp.isNotEmpty()) return "< ${stopResp.take(200)}"
        return ""
    }

    private fun buildBadgeString(state: JSONObject): String {
        val history = state.optJSONArray("hookHistory") ?: return ""
        var tools = 0; var perms = 0; var sessions = 0
        for (i in 0 until history.length()) {
            val entry = history.optJSONObject(i) ?: continue
            val event = entry.optString("event", "")
            when {
                event in TOOL_EVENTS -> tools++
                event in PERM_EVENTS -> perms++
                event in SESSION_EVENTS -> sessions++
            }
        }
        val parts = mutableListOf<String>()
        if (tools > 0) parts.add("T:$tools")
        if (perms > 0) parts.add("P:$perms")
        if (sessions > 0) parts.add("S:$sessions")
        return parts.joinToString(" ")
    }
}
