package com.jishisongfu.duobibi.util

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.Uri
import java.io.ByteArrayOutputStream
import java.io.InputStream

/**
 * 上传 OCR 前的图片压缩工具。
 *
 * 目标:JPEG quality 85,短边 ≤ 1024px,最终 < 5MB(后端 MAX_IMAGE_BYTES 上限)。
 * 在内存里完成不落盘,避免缓存目录管理。
 *
 * EXIF 旋转:相机出来的图有 orientation EXIF,直接 decode 不旋转会得到躺平的图。
 * 这里检 EXIF 旋转一次,让传给 LLM 的图是正方向(影响识别准确率,小红书拍照常见竖屏)。
 */
object ImageCompressor {

    private const val TARGET_SHORT_EDGE = 1024
    private const val JPEG_QUALITY = 85
    private const val MAX_BYTES = 5 * 1024 * 1024

    /**
     * 从 Uri 加载图片 → 旋转修正 → 缩到短边 ≤ 1024 → JPEG quality 85 编码 → 返回字节。
     *
     * 失败抛 IOException(Uri 无效 / 解码失败 / OOM 等),调用方需 try/catch。
     */
    fun compress(ctx: Context, uri: Uri): ByteArray {
        // 先读 bounds 算出 inSampleSize 避免大图 OOM
        val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
        ctx.contentResolver.openInputStream(uri).use { stream: InputStream? ->
            BitmapFactory.decodeStream(stream, null, bounds)
        }
        if (bounds.outWidth <= 0 || bounds.outHeight <= 0) {
            throw IllegalArgumentException("decode bounds failed for $uri")
        }
        val shortEdge = minOf(bounds.outWidth, bounds.outHeight)
        // inSampleSize 必须是 2 的幂(BitmapFactory 文档),逐次倍增直到短边 ≤ 1024
        var sample = 1
        while (shortEdge / (sample * 2) >= TARGET_SHORT_EDGE) sample *= 2

        val decoded = ctx.contentResolver.openInputStream(uri).use { stream ->
            BitmapFactory.decodeStream(
                stream, null,
                BitmapFactory.Options().apply { inSampleSize = sample },
            )
        } ?: throw IllegalStateException("decode bitmap failed for $uri")

        // 再精确缩到短边 1024(inSampleSize 只能 2 倍粗调,这里 createScaledBitmap 精调)
        val scaled = scaleToShortEdge(decoded, TARGET_SHORT_EDGE)
        if (scaled !== decoded) decoded.recycle()

        // EXIF 旋转
        val rotated = applyExifRotation(ctx, uri, scaled)
        if (rotated !== scaled) scaled.recycle()

        return rotated.use { bmp ->
            val baos = ByteArrayOutputStream(256 * 1024)
            bmp.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, baos)
            val bytes = baos.toByteArray()
            if (bytes.size > MAX_BYTES) {
                // 极端情况 quality 85 + 1024 短边还超 5MB(超大像素 + 高频纹理)→ 降 quality 重压
                val baos2 = ByteArrayOutputStream(256 * 1024)
                bmp.compress(Bitmap.CompressFormat.JPEG, 60, baos2)
                baos2.toByteArray()
            } else {
                bytes
            }
        }
    }

    private fun scaleToShortEdge(src: Bitmap, target: Int): Bitmap {
        val w = src.width
        val h = src.height
        val short = minOf(w, h)
        if (short <= target) return src
        val scale = target.toFloat() / short
        return Bitmap.createScaledBitmap(src, (w * scale).toInt(), (h * scale).toInt(), true)
    }

    private fun applyExifRotation(ctx: Context, uri: Uri, bmp: Bitmap): Bitmap {
        val orientation = try {
            ctx.contentResolver.openInputStream(uri).use { stream ->
                stream ?: return bmp
                ExifInterface(stream).getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL,
                )
            }
        } catch (_: Exception) {
            return bmp
        }
        val degrees = when (orientation) {
            ExifInterface.ORIENTATION_ROTATE_90 -> 90f
            ExifInterface.ORIENTATION_ROTATE_180 -> 180f
            ExifInterface.ORIENTATION_ROTATE_270 -> 270f
            else -> return bmp
        }
        val matrix = Matrix().apply { postRotate(degrees) }
        return Bitmap.createBitmap(bmp, 0, 0, bmp.width, bmp.height, matrix, true)
    }

    /** Kotlin's stdlib doesn't have Bitmap.use, but we need recycle-on-scope-exit semantics. */
    private inline fun <R> Bitmap.use(block: (Bitmap) -> R): R {
        try {
            return block(this)
        } finally {
            if (!isRecycled) recycle()
        }
    }
}
