package app.closer.core.media

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream

/**
 * Downscales + JPEG-compresses photos from the gallery/camera before they're encrypted and
 * uploaded, to cut upload time and storage. Deliberately leaves **animated GIFs and already-small
 * images untouched** (so keyboard GIFs/stickers keep their animation + transparency — those never
 * reach this path anyway) and applies **EXIF orientation** so photos aren't sent sideways.
 *
 * Never throws: on any failure it returns the original bytes, so a photo is never lost.
 */
object MediaCompressor {
    private const val MAX_DIM = 1600
    private const val JPEG_QUALITY = 80
    private const val SKIP_BELOW_BYTES = 200 * 1024

    fun compressPhoto(input: ByteArray): ByteArray = runCatching {
        if (isGif(input)) return input

        val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
        BitmapFactory.decodeByteArray(input, 0, input.size, bounds)
        val w = bounds.outWidth
        val h = bounds.outHeight
        if (w <= 0 || h <= 0) return input
        // Already small — leave it (also preserves small stickers/PNGs picked from the gallery).
        if (w <= MAX_DIM && h <= MAX_DIM && input.size <= SKIP_BELOW_BYTES) return input

        val opts = BitmapFactory.Options().apply { inSampleSize = sampleSize(w, h, MAX_DIM) }
        var bmp = BitmapFactory.decodeByteArray(input, 0, input.size, opts) ?: return input

        // Exact-fit downscale on the long edge.
        val longEdge = maxOf(bmp.width, bmp.height)
        if (longEdge > MAX_DIM) {
            val scale = MAX_DIM.toFloat() / longEdge
            val scaled = Bitmap.createScaledBitmap(bmp, (bmp.width * scale).toInt(), (bmp.height * scale).toInt(), true)
            if (scaled != bmp) bmp.recycle()
            bmp = scaled
        }

        // Re-apply EXIF rotation (lost when decoding to a bitmap).
        val rotation = orientationDegrees(input)
        if (rotation != 0f) {
            val m = Matrix().apply { postRotate(rotation) }
            val rotated = Bitmap.createBitmap(bmp, 0, 0, bmp.width, bmp.height, m, true)
            if (rotated != bmp) bmp.recycle()
            bmp = rotated
        }

        val out = ByteArrayOutputStream()
        bmp.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, out)
        bmp.recycle()
        val result = out.toByteArray()
        if (result.isNotEmpty() && result.size < input.size) result else input
    }.getOrDefault(input)

    private fun isGif(b: ByteArray): Boolean =
        b.size >= 3 && b[0] == 'G'.code.toByte() && b[1] == 'I'.code.toByte() && b[2] == 'F'.code.toByte()

    private fun sampleSize(w: Int, h: Int, target: Int): Int {
        var s = 1
        val longEdge = maxOf(w, h)
        while (longEdge / (s * 2) >= target) s *= 2
        return s
    }

    private fun orientationDegrees(input: ByteArray): Float = runCatching {
        val exif = ExifInterface(ByteArrayInputStream(input))
        when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
            ExifInterface.ORIENTATION_ROTATE_90 -> 90f
            ExifInterface.ORIENTATION_ROTATE_180 -> 180f
            ExifInterface.ORIENTATION_ROTATE_270 -> 270f
            else -> 0f
        }
    }.getOrDefault(0f)
}
