/*
 * Hermes Node Android
 * Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package net.nexlab.hermesnodeandroid.capabilities

import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.ImageFormat
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.media.ImageReader
import android.os.Handler
import android.os.HandlerThread
import androidx.core.content.ContextCompat
import net.nexlab.hermesnodeandroid.CapabilityResult
import net.nexlab.hermesnodeandroid.ConfigStore
import net.nexlab.hermesnodeandroid.VideoCaptureActivity
import net.nexlab.hermesnodeandroid.VideoCaptureSessionStore
import org.json.JSONArray
import org.json.JSONObject
import java.util.Base64
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

class AndroidCameraControl(private val context: Context) {
    fun capabilityInfo(): JSONObject = JSONObject()
        .put("available", true)
        .put("can_capture", hasCameraPermission())
        .put("can_inject", false)
        .put("capture_requires_camera_permission", !hasCameraPermission())
        .put("capture_video_requires_visible_activity", true)
        .put("backend", "Camera2")

    fun handle(action: String?, params: JSONObject): CapabilityResult = when (action) {
        "list_cameras" -> listCameras()
        "get_camera_status" -> CapabilityResult.ok("available" to true, "backend" to "Camera2", "can_capture" to hasCameraPermission())
        "capture_frame" -> captureFrame(params)
        "capture_video" -> captureVideo(params)
        "inject_video" -> CapabilityResult.unsupported("inject_video is not a normal Android capability without rooted/vendor-specific backend")
        else -> CapabilityResult.unsupported("Unknown camera_control action: $action")
    }

    private fun listCameras(): CapabilityResult {
        val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        val arr = JSONArray()
        manager.cameraIdList.forEach { id ->
            val chars = manager.getCameraCharacteristics(id)
            val facing = when (chars.get(CameraCharacteristics.LENS_FACING)) {
                CameraCharacteristics.LENS_FACING_FRONT -> "front"
                CameraCharacteristics.LENS_FACING_BACK -> "back"
                CameraCharacteristics.LENS_FACING_EXTERNAL -> "external"
                else -> "unknown"
            }
            val sizes = chars.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                ?.getOutputSizes(ImageFormat.JPEG)
                ?.take(10)
                ?.map { JSONObject().put("width", it.width).put("height", it.height) }
                ?: emptyList()
            arr.put(JSONObject().put("id", id).put("facing", facing).put("jpeg_sizes", JSONArray(sizes)))
        }
        return CapabilityResult.ok("cameras" to arr, "count" to arr.length())
    }

    private fun captureFrame(params: JSONObject): CapabilityResult {
        if (!hasCameraPermission()) return CapabilityResult.unsupported("capture_frame requires CAMERA permission; grant Camera permission in Android settings/app prompt")
        val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        val cameraId = params.optString("camera_id", params.optString("id", "0"))
        val width = params.optInt("width", 640)
        val height = params.optInt("height", 480)
        val thread = HandlerThread("HermesCameraCapture").apply { start() }
        val handler = Handler(thread.looper)
        val imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1)
        val jpegQueue = ArrayBlockingQueue<ByteArray>(1)
        imageReader.setOnImageAvailableListener({ reader ->
            val image = reader.acquireLatestImage() ?: return@setOnImageAvailableListener
            image.use {
                val buffer = it.planes[0].buffer
                val bytes = ByteArray(buffer.remaining())
                buffer.get(bytes)
                jpegQueue.offer(bytes)
            }
        }, handler)

        var camera: CameraDevice? = null
        var session: CameraCaptureSession? = null
        return try {
            val opened = CountDownLatch(1)
            manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
                override fun onOpened(device: CameraDevice) { camera = device; opened.countDown() }
                override fun onDisconnected(device: CameraDevice) { device.close(); opened.countDown() }
                override fun onError(device: CameraDevice, error: Int) { device.close(); opened.countDown() }
            }, handler)
            if (!opened.await(3, TimeUnit.SECONDS) || camera == null) return CapabilityResult.unsupported("capture_frame timed out opening camera $cameraId")

            val configured = CountDownLatch(1)
            camera!!.createCaptureSession(listOf(imageReader.surface), object : CameraCaptureSession.StateCallback() {
                override fun onConfigured(captureSession: CameraCaptureSession) { session = captureSession; configured.countDown() }
                override fun onConfigureFailed(captureSession: CameraCaptureSession) { configured.countDown() }
            }, handler)
            if (!configured.await(3, TimeUnit.SECONDS) || session == null) return CapabilityResult.unsupported("capture_frame timed out configuring camera $cameraId")

            val request = camera!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
                addTarget(imageReader.surface)
            }.build()
            session!!.capture(request, null, handler)
            val jpeg = jpegQueue.poll(5, TimeUnit.SECONDS) ?: return CapabilityResult.unsupported("capture_frame timed out waiting for JPEG")
            CapabilityResult.ok(
                "camera_id" to cameraId,
                "format" to "jpg",
                "encoding" to "base64",
                "width" to width,
                "height" to height,
                "data" to Base64.getEncoder().encodeToString(jpeg),
                "bytes" to jpeg.size,
            )
        } catch (e: SecurityException) {
            CapabilityResult.unsupported("capture_frame permission denied: ${e.message}")
        } catch (e: CameraAccessException) {
            CapabilityResult.unsupported("capture_frame camera access failed: ${e.message}")
        } catch (e: Exception) {
            CapabilityResult.unsupported("capture_frame failed: ${e.message}")
        } finally {
            runCatching { session?.close() }
            runCatching { camera?.close() }
            runCatching { imageReader.close() }
            thread.quitSafely()
        }
    }

    private fun captureVideo(params: JSONObject): CapabilityResult {
        if (!hasCameraPermission()) {
            return CapabilityResult.unsupported("capture_video requires CAMERA permission; grant Camera permission in Android settings/app prompt")
        }
        val requestId = params.optString("request_id").ifBlank { "capture-video-${System.currentTimeMillis()}" }
        val existing = VideoCaptureSessionStore.consumeResult(context, requestId)
        if (existing != null) {
            return if (existing.optBoolean("success", false)) {
                CapabilityResult.ok(
                    "request_id" to requestId,
                    "completed" to true,
                    "path" to existing.optString("path"),
                    "bytes" to existing.optLong("bytes"),
                    "format" to existing.optString("format", "mp4"),
                    "duration_ms" to existing.optLong("duration_ms"),
                    "width" to existing.optInt("width"),
                    "height" to existing.optInt("height"),
                    "camera_id" to existing.optString("camera_id"),
                    "message" to existing.optString("message", "Video capture complete"),
                )
            } else {
                CapabilityResult.unsupported(existing.optString("error", "capture_video failed"))
            }
        }

        val payload = JSONObject()
            .put("request_id", requestId)
            .put("node_name", ConfigStore.load(context).nodeName)
            .put("duration_ms", params.optLong("duration_ms", 3000L).coerceIn(1000L, 15000L))
            .put("width", params.optInt("width", 1280))
            .put("height", params.optInt("height", 720))
            .put("camera_id", params.optString("camera_id", params.optString("id", "")))
        VideoCaptureSessionStore.savePending(context, payload)
        val intent = Intent(context, VideoCaptureActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        context.startActivity(intent)
        return CapabilityResult.ok(
            "request_id" to requestId,
            "pending_user_visible_capture" to true,
            "message" to "Visible recording activity launched; call capture_video again with the same request_id to fetch the result after completion",
        )
    }

    private fun hasCameraPermission(): Boolean =
        ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
}
