package com.bytelyst.platform

import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.UUID

/**
 * Telemetry client for platform-service.
 *
 * Queues events locally and flushes in batches to POST /api/telemetry/events.
 * Events persist in SharedPreferences to survive process death.
 * Fire-and-forget: errors never surface to the user.
 *
 * Mirrors the Swift BLTelemetryClient API.
 */
class BLTelemetryClient(
    private val config: BLPlatformConfig,
    context: Context,
    private val maxQueueSize: Int = 50,
    private val flushIntervalMs: Long = 30_000L,
) {
    // ── Event schema ─────────────────────────────────────────

    @Serializable
    data class TelemetryEvent(
        val id: String,
        val productId: String,
        val anonymousInstallId: String,
        val sessionId: String,
        val platform: String,
        val channel: String,
        val osFamily: String,
        val osVersion: String,
        val appVersion: String,
        val buildNumber: String,
        val releaseChannel: String,
        val eventType: String,
        val module: String,
        val eventName: String,
        val feature: String? = null,
        val message: String? = null,
        val errorCode: String? = null,
        val errorDomain: String? = null,
        val tags: Map<String, String>? = null,
        val metrics: Map<String, Double>? = null,
        val occurredAt: String,
    )

    @Serializable
    private data class EventBatch(val events: List<TelemetryEvent>)

    // ── State ────────────────────────────────────────────────

    private val prefs: SharedPreferences =
        context.getSharedPreferences("${config.productId}_telemetry", Context.MODE_PRIVATE)
    private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true }
    private val queue = mutableListOf<TelemetryEvent>()
    private var flushJob: Job? = null
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    private var sessionId = UUID.randomUUID().toString()

    private val client = BLPlatformClient(config)

    private val installId: String by lazy {
        val key = "install_id"
        prefs.getString(key, null) ?: UUID.randomUUID().toString().also {
            prefs.edit().putString(key, it).apply()
        }
    }

    private val osVersion = "Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})"
    private val appVersion: String
    private val buildNumber: String

    init {
        val pm = context.packageManager
        val pi = try { pm.getPackageInfo(context.packageName, 0) } catch (_: Exception) { null }
        appVersion = pi?.versionName ?: "0.0.0"
        buildNumber = (pi?.longVersionCode ?: 0).toString()
    }

    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())

    // ── Lifecycle ────────────────────────────────────────────

    fun start() {
        if (flushJob != null) return
        flushJob = scope.launch {
            while (isActive) {
                delay(flushIntervalMs)
                flush()
            }
        }
    }

    fun stop() {
        flush()
        flushJob?.cancel()
        flushJob = null
    }

    fun newSession() {
        sessionId = UUID.randomUUID().toString()
    }

    // ── Public API ───────────────────────────────────────────

    fun trackEvent(
        eventType: String,
        module: String,
        name: String,
        feature: String? = null,
        message: String? = null,
        errorCode: String? = null,
        errorDomain: String? = null,
        tags: Map<String, String>? = null,
        metrics: Map<String, Double>? = null,
    ) {
        val event = TelemetryEvent(
            id = UUID.randomUUID().toString(),
            productId = config.productId,
            anonymousInstallId = installId,
            sessionId = sessionId,
            platform = config.platform,
            channel = config.channel,
            osFamily = "android",
            osVersion = osVersion,
            appVersion = appVersion,
            buildNumber = buildNumber,
            releaseChannel = "beta",
            eventType = eventType,
            module = module,
            eventName = name,
            feature = feature,
            message = message?.take(512),
            errorCode = errorCode,
            errorDomain = errorDomain,
            tags = tags,
            metrics = metrics,
            occurredAt = isoNow(),
        )

        synchronized(queue) {
            queue.add(event)
            if (queue.size >= maxQueueSize) {
                flush()
            }
        }
    }

    fun trackScreen(screen: String) {
        trackEvent("info", "navigation", "screen_view", tags = mapOf("screen" to screen))
    }

    // ── Flush ────────────────────────────────────────────────

    fun flush() {
        val batch: List<TelemetryEvent>
        synchronized(queue) {
            if (queue.isEmpty()) return
            batch = queue.toList()
            queue.clear()
        }

        scope.launch {
            val body = json.encodeToString(EventBatch(batch))
            client.fireAndForget(
                "POST", "/api/telemetry/events", body,
                extraHeaders = mapOf("X-Install-Token" to installId),
            )
        }
    }
}
