package dev.jasonpearson.automobile.sdk

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.util.Base64
import androidx.core.app.NotificationCompat
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import kotlin.math.min
import kotlin.math.roundToInt

/** Notification styles supported by AutoMobile. */
enum class NotificationStyle {
  DEFAULT,
  BIG_TEXT,
  BIG_PICTURE;

  companion object {
    fun fromWireName(value: String?): NotificationStyle {
      return when (value?.lowercase()) {
        "bigtext" -> BIG_TEXT
        "bigpicture" -> BIG_PICTURE
        else -> DEFAULT
      }
    }
  }
}

/**
 * An action button attached to a notification.
 *
 * @property label The text displayed on the action button
 * @property actionId A unique identifier broadcast when the user taps the action
 */
data class NotificationAction(val label: String, val actionId: String)

/** Debug-focused notification helper for AutoMobile tests. Requires app-under-test integration. */
object AutoMobileNotifications {
  private const val TAG = "AutoMobileNotifications"

  const val ACTION_POST_NOTIFICATION = "dev.jasonpearson.automobile.sdk.NOTIFICATION_POST"
  const val ACTION_NOTIFICATION_ACTION = "dev.jasonpearson.automobile.sdk.NOTIFICATION_ACTION"

  const val EXTRA_TITLE = "title"
  const val EXTRA_BODY = "body"
  const val EXTRA_STYLE = "style"
  const val EXTRA_IMAGE_PATH = "image_path"
  const val EXTRA_ACTIONS = "actions_json"
  const val EXTRA_CHANNEL_ID = "channel_id"
  const val EXTRA_ACTION_ID = "action_id"
  const val EXTRA_NOTIFICATION_ID = "notification_id"

  private const val DEFAULT_CHANNEL_ID = "automobile_test_notifications"
  private const val DEFAULT_CHANNEL_NAME = "AutoMobile Test Notifications"
  private const val DEFAULT_CHANNEL_DESCRIPTION = "Notifications generated by AutoMobile tests"
  private const val DEFAULT_IMAGE_DIR = "/sdcard/Download/automobile"
  private const val MAX_IMAGE_DIMENSION = 1024
  private const val ACTION_ICON_RES = android.R.drawable.ic_menu_view

  private var context: Context? = null

  /**
   * Initialize with application context. Called automatically by [AutoMobileSDK.initialize].
   *
   * @param context Application context
   */
  fun initialize(context: Context) {
    this.context = context.applicationContext
  }

  /**
   * Post a notification to the system notification tray.
   *
   * @param title Notification title
   * @param body Notification body text
   * @param style Visual style (default, big text, or big picture)
   * @param imagePath Path or base64 data for big picture style
   * @param actions Actionable buttons to include on the notification
   * @param channelId Custom notification channel ID (uses default if null)
   * @return true if the notification was posted successfully
   */
  fun post(
      title: String,
      body: String,
      style: NotificationStyle = NotificationStyle.DEFAULT,
      imagePath: String? = null,
      actions: List<NotificationAction> = emptyList(),
      channelId: String? = null,
  ): Boolean {
    val ctx = context
    if (ctx == null) {
      AutoMobileSDK.logger.w(TAG) {
          "AutoMobileNotifications not initialized; call AutoMobileSDK.initialize() or AutoMobileNotifications.initialize()."
      }
      return false
    }

    return postWithContext(ctx, title, body, style, imagePath, actions, channelId)
  }

  internal fun postWithContext(
      context: Context,
      title: String,
      body: String,
      style: NotificationStyle,
      imagePath: String?,
      actions: List<NotificationAction>,
      channelId: String?,
  ): Boolean {
    return try {
      val notificationManager =
          context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
      val effectiveChannelId = channelId?.takeIf { it.isNotBlank() } ?: DEFAULT_CHANNEL_ID
      ensureChannel(notificationManager, effectiveChannelId, channelId)

      val builder =
          NotificationCompat.Builder(context, effectiveChannelId)
              .setContentTitle(title)
              .setContentText(body)
              .setSmallIcon(resolveSmallIcon(context))
              .setAutoCancel(true)

      when (style) {
        NotificationStyle.BIG_TEXT -> {
          builder.setStyle(NotificationCompat.BigTextStyle().bigText(body))
        }
        NotificationStyle.BIG_PICTURE -> {
          val bitmap = imagePath?.let { loadBitmap(context, it) }
          if (bitmap != null) {
            builder
                .setStyle(NotificationCompat.BigPictureStyle().bigPicture(bitmap))
                .setLargeIcon(bitmap)
          } else {
            AutoMobileSDK.logger.w(TAG) { "Big picture style requested but image could not be loaded." }
            builder.setStyle(NotificationCompat.BigTextStyle().bigText(body))
          }
        }
        NotificationStyle.DEFAULT -> {
          // No additional styling.
        }
      }

      val notificationId = (System.currentTimeMillis() and 0x7FFFFFFF).toInt()
      actions.forEach { action ->
        if (action.label.isBlank() || action.actionId.isBlank()) {
          return@forEach
        }
        builder.addAction(createAction(context, notificationId, action))
      }

      notificationManager.notify(notificationId, builder.build())
      true
    } catch (e: Exception) {
      AutoMobileSDK.logger.e(TAG, e) { "Failed to post notification" }
      false
    }
  }

  private fun ensureChannel(
      notificationManager: NotificationManager,
      channelId: String,
      customChannelId: String?,
  ) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return

    val name =
        if (customChannelId != null && customChannelId.isNotBlank()) {
          "AutoMobile: $customChannelId"
        } else {
          DEFAULT_CHANNEL_NAME
        }
    val description = DEFAULT_CHANNEL_DESCRIPTION

    val channel =
        NotificationChannel(channelId, name, NotificationManager.IMPORTANCE_DEFAULT).apply {
          setDescription(description)
        }

    notificationManager.createNotificationChannel(channel)
  }

  private fun createAction(
      context: Context,
      notificationId: Int,
      action: NotificationAction,
  ): NotificationCompat.Action {
    val intent =
        Intent(ACTION_NOTIFICATION_ACTION).apply {
          setPackage(context.packageName)
          putExtra(EXTRA_ACTION_ID, action.actionId)
          putExtra(EXTRA_NOTIFICATION_ID, notificationId)
        }

    val flags =
        PendingIntent.FLAG_UPDATE_CURRENT or
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0

    val pendingIntent =
        PendingIntent.getBroadcast(context, action.actionId.hashCode(), intent, flags)

    return NotificationCompat.Action.Builder(ACTION_ICON_RES, action.label, pendingIntent).build()
  }

  private fun resolveSmallIcon(context: Context): Int {
    val icon = context.applicationInfo.icon
    return if (icon != 0) icon else android.R.drawable.stat_notify_more
  }

  private fun loadBitmap(context: Context, imagePath: String): Bitmap? {
    val trimmed = imagePath.trim()
    if (trimmed.isEmpty()) return null

    if (trimmed.startsWith("data:") && trimmed.contains(",")) {
      val base64Data = trimmed.substringAfter(",")
      return decodeBitmapFromBase64(base64Data)
    }

    if (trimmed.startsWith("base64:")) {
      return decodeBitmapFromBase64(trimmed.removePrefix("base64:"))
    }

    if (trimmed.startsWith("content://")) {
      return try {
        decodeBitmapFromStream(
            context.contentResolver.openInputStream(android.net.Uri.parse(trimmed))
        )
      } catch (e: Exception) {
        AutoMobileSDK.logger.w(TAG, e) { "Failed to open content URI for image" }
        null
      }
    }

    val candidatePaths = mutableListOf<String>()
    when {
      trimmed.startsWith("file://") -> candidatePaths.add(trimmed.removePrefix("file://"))
      trimmed.startsWith("/") -> candidatePaths.add(trimmed)
      else -> {
        candidatePaths.add(trimmed)
        candidatePaths.add("$DEFAULT_IMAGE_DIR/$trimmed")
      }
    }

    for (path in candidatePaths) {
      val file = File(path)
      if (file.exists()) {
        try {
          FileInputStream(file).use { stream ->
            return decodeBitmapFromStream(stream)
          }
        } catch (e: Exception) {
          AutoMobileSDK.logger.w(TAG, e) { "Failed to read image file at $path" }
        }
      }
    }

    return decodeBitmapFromBase64(trimmed)
  }

  private fun decodeBitmapFromBase64(base64Data: String): Bitmap? {
    return try {
      val bytes = Base64.decode(base64Data, Base64.DEFAULT)
      decodeBitmapFromBytes(bytes)
    } catch (e: Exception) {
      AutoMobileSDK.logger.w(TAG, e) { "Failed to decode base64 image" }
      null
    }
  }

  private fun decodeBitmapFromStream(stream: InputStream?): Bitmap? {
    if (stream == null) return null
    stream.use {
      return decodeBitmapFromBytes(it.readBytes())
    }
  }

  private fun decodeBitmapFromBytes(bytes: ByteArray): Bitmap? {
    if (bytes.isEmpty()) return null
    val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
    BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bounds)
    if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null

    val options =
        BitmapFactory.Options().apply {
          inSampleSize =
              calculateInSampleSize(bounds.outWidth, bounds.outHeight, MAX_IMAGE_DIMENSION)
        }

    val decoded = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options) ?: return null
    return scaleBitmap(decoded, MAX_IMAGE_DIMENSION)
  }

  private fun calculateInSampleSize(width: Int, height: Int, maxDim: Int): Int {
    var inSampleSize = 1
    if (height > maxDim || width > maxDim) {
      var halfHeight = height / 2
      var halfWidth = width / 2
      while (halfHeight / inSampleSize >= maxDim || halfWidth / inSampleSize >= maxDim) {
        inSampleSize *= 2
      }
    }
    return inSampleSize
  }

  private fun scaleBitmap(bitmap: Bitmap, maxDim: Int): Bitmap {
    val width = bitmap.width
    val height = bitmap.height
    if (width <= maxDim && height <= maxDim) {
      return bitmap
    }
    val ratio = min(maxDim.toFloat() / width, maxDim.toFloat() / height)
    val newWidth = (width * ratio).roundToInt().coerceAtLeast(1)
    val newHeight = (height * ratio).roundToInt().coerceAtLeast(1)
    return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
  }
}
