package com.centralcloud.oncall.sms

import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioManager
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.BatteryManager
import android.os.PowerManager
import android.util.Log
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.centralcloud.oncall.util.HttpUtil
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit

/**
 * Periodic heartbeat to centralcloud-ops so the team can SEE this device is
 * alive and the SMS relay is in the expected state. Runs once every 15
 * minutes (Android WorkManager's minimum periodic interval) when the device
 * has network connectivity.
 *
 *   POST {baseUrl}/api/devices/heartbeat
 *   X-Device-Id: <token>
 *   {
 *     "device_id":  "<token>",
 *     "sms_relay":  {
 *       "enabled":           bool,
 *       "whitelist_size":    int,
 *       "last_forward_at":   iso8601 | null,
 *       "failure_count_24h": int
 *     },
 *     "device_state": {
 *       "battery_level":    0.0..1.0,
 *       "is_charging":      bool,
 *       "network_type":     "wifi" | "cellular" | "ethernet" | "none",
 *       "low_power_mode":   bool,
 *       "dnd_active":       bool,        // omitted on older Android
 *       "ringer_mode":      "silent" | "vibrate" | "normal"
 *     }
 *   }
 *
 * Scheduled via [scheduleIfConfigured]. Skips heartbeat (returns success)
 * if relay isn't configured yet — we don't want to spam an unconfigured
 * server.
 */
class DeviceHeartbeatWorker(appContext: Context, params: WorkerParameters) :
    CoroutineWorker(appContext, params) {

    override suspend fun doWork(): Result {
        val prefs = SmsRelayPreferences(applicationContext)

        if (prefs.baseUrl.isBlank() || prefs.deviceId.isBlank()) {
            Log.d(TAG, "skip: base_url or device_id unset")
            return Result.success()
        }

        return try {
            val payload = buildPayload(prefs).toString()
            val url = "${prefs.baseUrl}/api/devices/heartbeat"
            val client = HttpUtil.defaultClient(applicationContext, prefs.baseUrl)
            val request = HttpUtil.requestBuilder(url)
                .addHeader("X-Device-Id", prefs.deviceId)
                .post(payload.toRequestBody(JSON))
                .build()

            client.newCall(request).execute().use { response ->
                when {
                    response.isSuccessful -> {
                        // 24h reset on each successful heartbeat is a rough
                        // approximation. Real "last 24h" is computed server-side
                        // when we add proper rolling windows.
                        prefs.resetFailureCount()
                        Log.d(TAG, "heartbeat ok code=${response.code}")
                        Result.success()
                    }

                    response.code in 400..499 -> {
                        // Bad device_id / bad payload — won't recover by retrying.
                        Log.w(TAG, "heartbeat permanent reject code=${response.code}")
                        Result.failure()
                    }

                    else -> {
                        Log.w(TAG, "heartbeat transient fail code=${response.code}")
                        Result.retry()
                    }
                }
            }
        } catch (t: Throwable) {
            Log.w(TAG, "heartbeat exception", t)
            Result.retry()
        }
    }

    private fun buildPayload(prefs: SmsRelayPreferences): JSONObject {
        val smsRelay = JSONObject()
            .put("enabled", prefs.enabled)
            .put("whitelist_size", prefs.whitelist.size)
            .put("failure_count_24h", prefs.failureCount24h)
            .apply {
                val ts = prefs.lastForwardAtMs
                put("last_forward_at", if (ts > 0L) iso8601(ts) else JSONObject.NULL)
            }

        val deviceState = collectDeviceState()

        return JSONObject()
            .put("device_id", prefs.deviceId)
            .put("sms_relay", smsRelay)
            .put("device_state", deviceState)
    }

    private fun collectDeviceState(): JSONObject {
        val ctx = applicationContext
        val obj = JSONObject()

        // Battery
        try {
            val intent: Intent? =
                ctx.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            if (intent != null) {
                val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
                val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
                if (level >= 0 && scale > 0) {
                    obj.put("battery_level", level.toFloat() / scale.toFloat())
                }
                val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
                obj.put(
                    "is_charging",
                    status == BatteryManager.BATTERY_STATUS_CHARGING ||
                        status == BatteryManager.BATTERY_STATUS_FULL
                )
            }
        } catch (_: Throwable) {}

        // Network
        try {
            val cm = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val active = cm.activeNetwork
            val caps = active?.let { cm.getNetworkCapabilities(it) }
            val netType = when {
                caps == null -> "none"
                caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
                caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular"
                caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet"
                else -> "other"
            }
            obj.put("network_type", netType)
        } catch (_: Throwable) {}

        // Low-power mode
        try {
            val pm = ctx.getSystemService(Context.POWER_SERVICE) as PowerManager
            obj.put("low_power_mode", pm.isPowerSaveMode)
        } catch (_: Throwable) {}

        // DND — readable without notification policy access on API 23+.
        try {
            val nm = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            val filter = nm.currentInterruptionFilter
            obj.put(
                "dnd_active",
                filter == NotificationManager.INTERRUPTION_FILTER_PRIORITY ||
                    filter == NotificationManager.INTERRUPTION_FILTER_NONE ||
                    filter == NotificationManager.INTERRUPTION_FILTER_ALARMS
            )
        } catch (_: Throwable) {}

        // Ringer mode
        try {
            val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
            val mode = when (am.ringerMode) {
                AudioManager.RINGER_MODE_SILENT -> "silent"
                AudioManager.RINGER_MODE_VIBRATE -> "vibrate"
                AudioManager.RINGER_MODE_NORMAL -> "normal"
                else -> "unknown"
            }
            obj.put("ringer_mode", mode)
        } catch (_: Throwable) {}

        return obj
    }

    private fun iso8601(ms: Long): String {
        val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
        df.timeZone = TimeZone.getTimeZone("UTC")
        return df.format(Date(ms))
    }

    companion object {
        private const val TAG = "DeviceHeartbeatWorker"
        const val UNIQUE_NAME = "centralcloud-ops-heartbeat"

        private val JSON = "application/json; charset=utf-8".toMediaType()

        /**
         * Schedule the periodic heartbeat. Idempotent — safe to call from
         * Application.onCreate or anywhere else; uses KEEP policy so existing
         * scheduled work is preserved across restarts.
         *
         * 15-minute interval is WorkManager's minimum periodic interval.
         */
        fun scheduleIfConfigured(context: Context) {
            val req = PeriodicWorkRequestBuilder<DeviceHeartbeatWorker>(15, TimeUnit.MINUTES)
                .setConstraints(
                    Constraints.Builder()
                        .setRequiredNetworkType(NetworkType.CONNECTED)
                        .build()
                )
                .addTag(UNIQUE_NAME)
                .build()

            WorkManager
                .getInstance(context.applicationContext)
                .enqueueUniquePeriodicWork(
                    UNIQUE_NAME,
                    ExistingPeriodicWorkPolicy.KEEP,
                    req
                )
        }

        /** Stop the periodic heartbeat (e.g. when user disables the relay). */
        fun cancel(context: Context) {
            WorkManager
                .getInstance(context.applicationContext)
                .cancelUniqueWork(UNIQUE_NAME)
        }
    }
}
