package com.bytelyst.platform

import android.content.Context
import android.content.SharedPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.UUID

/**
 * Crash reporter — captures uncaught exceptions and reports to telemetry.
 *
 * Installs a [Thread.UncaughtExceptionHandler] that:
 * 1. Saves crash info to SharedPreferences
 * 2. On next app launch, sends the crash report to platform-service telemetry
 * 3. Delegates to the previous handler (so the system can still show ANR/crash dialogs)
 *
 * Mirrors the Swift BLCrashReporter API (MetricKit equivalent for Android).
 */
class BLCrashReporter(
    context: Context,
    private val config: BLPlatformConfig,
) {
    private val prefs: SharedPreferences =
        context.getSharedPreferences("${config.productId}_crash_reporter", Context.MODE_PRIVATE)
    private val client = BLPlatformClient(config)
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private val json = Json { encodeDefaults = true }

    private val isoFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
        timeZone = TimeZone.getTimeZone("UTC")
    }

    /** Thread-safe ISO timestamp — SimpleDateFormat is NOT thread-safe. */
    @Synchronized
    private fun isoNow(): String = isoFormat.format(Date())

    companion object {
        private const val KEY_PENDING_CRASH = "pending_crash"
    }

    /**
     * Install the crash handler and send any pending crash reports.
     * Call once from Application.onCreate().
     */
    fun install() {
        sendPendingCrashReport()
        installHandler()
    }

    private fun installHandler() {
        val previousHandler = Thread.getDefaultUncaughtExceptionHandler()

        Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
            val sw = StringWriter()
            throwable.printStackTrace(PrintWriter(sw))

            val crashData = buildJsonObject {
                put("id", UUID.randomUUID().toString())
                put("productId", config.productId)
                put("platform", config.platform)
                put("timestamp", isoNow())
                put("thread", thread.name)
                put("exception", throwable.javaClass.name)
                put("message", throwable.message ?: "")
                put("stackTrace", sw.toString().take(4096))
            }

            // Persist synchronously (we may be about to die)
            prefs.edit().putString(KEY_PENDING_CRASH, json.encodeToString(crashData)).commit()

            // Delegate to previous handler
            previousHandler?.uncaughtException(thread, throwable)
        }
    }

    private fun sendPendingCrashReport() {
        val pending = prefs.getString(KEY_PENDING_CRASH, null) ?: return
        prefs.edit().remove(KEY_PENDING_CRASH).apply()

        scope.launch {
            try {
                val crashEvent = json.parseToJsonElement(pending)
                val payload = buildJsonObject {
                    put("productId", config.productId)
                    put("events", JsonArray(listOf(crashEvent)))
                }
                client.fireAndForget("POST", "/api/telemetry/events", json.encodeToString(payload))
            } catch (_: Exception) {
                // Best-effort — don't re-queue
            }
        }
    }
}
