package com.sentinelshield.mobile.camera

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleService
import com.sentinelshield.mobile.R
import org.json.JSONObject
import java.io.File
import java.time.Instant
import java.util.concurrent.Executor

class IncidentCameraService : LifecycleService() {
    private val mainExecutor: Executor by lazy { ContextCompat.getMainExecutor(this) }
    private var provider: ProcessCameraProvider? = null

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Digital Chaukidar protection active")
            .setContentText("Capturing a user-enabled security incident")
            .setOngoing(true)
            .setCategory(NotificationCompat.CATEGORY_SERVICE)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .build()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            startForeground(
                NOTIFICATION_ID,
                notification,
                ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA,
            )
        } else {
            startForeground(NOTIFICATION_ID, notification)
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        if (intent == null) {
            stopSelf(startId)
            return Service.START_NOT_STICKY
        }

        val commandId = intent.getStringExtra(EXTRA_COMMAND_ID).orEmpty()
        val incidentType = intent.getStringExtra(EXTRA_INCIDENT_TYPE).orEmpty()
        val burstCount = intent.getIntExtra(EXTRA_BURST_COUNT, 1).coerceIn(1, 5)
        if (!commandId.matches(Regex("^[A-Za-z0-9._:-]{16,128}$")) || incidentType.isBlank()) {
            stopSelf(startId)
            return Service.START_NOT_STICKY
        }

        bindAndCapture(
            commandId = commandId,
            incidentType = incidentType,
            frontCamera = intent.getBooleanExtra(EXTRA_FRONT_CAMERA, true),
            burstCount = burstCount,
            latitude = intent.optionalDouble(EXTRA_LATITUDE),
            longitude = intent.optionalDouble(EXTRA_LONGITUDE),
            startId = startId,
        )
        return Service.START_NOT_STICKY
    }

    override fun onDestroy() {
        provider?.unbindAll()
        provider = null
        super.onDestroy()
    }

    private fun bindAndCapture(
        commandId: String,
        incidentType: String,
        frontCamera: Boolean,
        burstCount: Int,
        latitude: Double?,
        longitude: Double?,
        startId: Int,
    ) {
        val future = ProcessCameraProvider.getInstance(this)
        future.addListener({
            try {
                val cameraProvider = future.get()
                provider = cameraProvider
                val selector = if (frontCamera) {
                    CameraSelector.DEFAULT_FRONT_CAMERA
                } else {
                    CameraSelector.DEFAULT_BACK_CAMERA
                }
                val capture = ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                    .setFlashMode(if (frontCamera) ImageCapture.FLASH_MODE_OFF else ImageCapture.FLASH_MODE_AUTO)
                    .build()
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(this, selector, capture)
                captureNext(
                    capture,
                    commandId,
                    incidentType,
                    frontCamera,
                    latitude,
                    longitude,
                    burstCount,
                    1,
                    startId,
                    mutableListOf(),
                )
            } catch (error: Exception) {
                finish(commandId, emptyList(), error.message, startId)
            }
        }, mainExecutor)
    }

    private fun captureNext(
        capture: ImageCapture,
        commandId: String,
        incidentType: String,
        frontCamera: Boolean,
        latitude: Double?,
        longitude: Double?,
        total: Int,
        index: Int,
        startId: Int,
        files: MutableList<String>,
    ) {
        val directory = File(filesDir, "incidents/$commandId").apply { mkdirs() }
        val output = File(directory, "capture_${index}_${System.currentTimeMillis()}.jpg")
        capture.takePicture(
            ImageCapture.OutputFileOptions.Builder(output).build(),
            mainExecutor,
            object : ImageCapture.OnImageSavedCallback {
                override fun onImageSaved(result: ImageCapture.OutputFileResults) {
                    files.add(output.absolutePath)
                    writeMetadata(directory, commandId, incidentType, frontCamera, latitude, longitude, files)
                    if (index < total) {
                        captureNext(capture, commandId, incidentType, frontCamera, latitude, longitude, total, index + 1, startId, files)
                    } else {
                        finish(commandId, files, null, startId)
                    }
                }

                override fun onError(exception: ImageCaptureException) {
                    finish(commandId, files, exception.message, startId)
                }
            },
        )
    }

    private fun writeMetadata(
        directory: File,
        commandId: String,
        incidentType: String,
        frontCamera: Boolean,
        latitude: Double?,
        longitude: Double?,
        files: List<String>,
    ) {
        val json = JSONObject()
            .put("command_id", commandId)
            .put("incident_type", incidentType)
            .put("camera", if (frontCamera) "front" else "rear")
            .put("captured_at", Instant.now().toString())
            .put("latitude", latitude)
            .put("longitude", longitude)
            .put("files", files)
        File(directory, "metadata.json").writeText(json.toString())
    }

    private fun finish(commandId: String, files: List<String>, error: String?, startId: Int) {
        sendBroadcast(Intent(ACTION_CAPTURE_COMPLETED).apply {
            setPackage(packageName)
            putExtra(EXTRA_COMMAND_ID, commandId)
            putExtra(EXTRA_CAPTURED_FILES, files.toTypedArray())
            putExtra(EXTRA_ERROR, error)
        })
        provider?.unbindAll()
        stopSelf(startId)
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            getSystemService(NotificationManager::class.java).createNotificationChannel(
                NotificationChannel(
                    CHANNEL_ID,
                    "Incident camera",
                    NotificationManager.IMPORTANCE_HIGH,
                ).apply {
                    description = "Visible status for user-enabled incident camera capture"
                },
            )
        }
    }

    private fun Intent.optionalDouble(name: String): Double? =
        if (hasExtra(name)) getDoubleExtra(name, 0.0) else null

    companion object {
        const val EXTRA_COMMAND_ID = "command_id"
        const val EXTRA_FRONT_CAMERA = "front_camera"
        const val EXTRA_BURST_COUNT = "burst_count"
        const val EXTRA_INCIDENT_TYPE = "incident_type"
        const val EXTRA_LATITUDE = "latitude"
        const val EXTRA_LONGITUDE = "longitude"
        const val EXTRA_CAPTURED_FILES = "captured_files"
        const val EXTRA_ERROR = "capture_error"
        const val ACTION_CAPTURE_COMPLETED = "com.digitalchaukidar.CAMERA_CAPTURE_COMPLETED"
        private const val CHANNEL_ID = "incident_camera"
        private const val NOTIFICATION_ID = 4102
    }
}
