package com.joe.camera.webcam.camera

import android.content.Context
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CaptureRequest
import android.provider.MediaStore
import android.util.Range
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.camera2.interop.Camera2CameraControl
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.CaptureRequestOptions
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.video.FileOutputOptions
import androidx.camera.video.PendingRecording
import androidx.camera.video.Quality
import androidx.camera.video.QualitySelector
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.camera.video.VideoRecordEvent
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.joe.camera.webcam.data.MediaRepository
import com.joe.camera.webcam.model.CameraState
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.io.File

class CameraController(private val context: Context, private val onState: (CameraState) -> Unit, private val onMessage: (String) -> Unit) {
    private val executor: Executor = ContextCompat.getMainExecutor(context)
    private val fileExecutor = Executors.newSingleThreadExecutor()
    private var provider: ProcessCameraProvider? = null
    private var preview: Preview? = null
    private var imageCapture: ImageCapture? = null
    private var videoCapture: VideoCapture<Recorder>? = null
    private var recording: Recording? = null
    private var afterRecordingStopped: (() -> Unit)? = null
    private var camera: Camera? = null
    private var lifecycleOwner: LifecycleOwner? = null
    private var previewView: PreviewView? = null
    var lensFacing: Int = CameraSelector.LENS_FACING_BACK
        private set
    var previewPaused = false
        private set
    private var linearZoom = 0f

    fun bind(owner: LifecycleOwner, view: PreviewView, facing: Int = lensFacing) {
        lifecycleOwner = owner
        previewView = view
        lensFacing = facing
        val future = ProcessCameraProvider.getInstance(context)
        future.addListener({
            runCatching {
                provider = future.get()
                provider?.unbindAll()
                preview = Preview.Builder().build().also { it.surfaceProvider = view.surfaceProvider }
                imageCapture = ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                    .setFlashMode(ImageCapture.FLASH_MODE_OFF)
                    .build()
                val recorder = Recorder.Builder().setQualitySelector(QualitySelector.from(Quality.FHD)).build()
                videoCapture = VideoCapture.withOutput(recorder)
                camera = provider?.bindToLifecycle(owner, CameraSelector.Builder().requireLensFacing(facing).build(), preview, imageCapture, videoCapture)
                camera?.cameraControl?.enableTorch(false)
                camera?.cameraControl?.setLinearZoom(linearZoom)
                camera?.let(::configureLowLight)
                onState(CameraState.FREE)
            }.onFailure { onState(CameraState.ERROR); onMessage("相机初始化失败：${it.message.orEmpty()}") }
        }, executor)
    }

    fun switchLens(facing: Int) { lifecycleOwner?.let { owner -> previewView?.let { bind(owner, it, facing) } } }

    fun togglePreview() {
        previewPaused = !previewPaused
        preview?.surfaceProvider = if (previewPaused) null else previewView?.surfaceProvider
    }

    fun setLinearZoom(value: Float) {
        linearZoom = value.coerceIn(0f, 1f)
        camera?.cameraControl?.setLinearZoom(linearZoom)
    }

    @OptIn(ExperimentalCamera2Interop::class)
    private fun configureLowLight(boundCamera: Camera) {
        runCatching {
            val cameraInfo = Camera2CameraInfo.from(boundCamera.cameraInfo)
            val availableFpsRanges = cameraInfo.getCameraCharacteristic(
                CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
            ) ?: emptyArray()
            val lowLightRange = availableFpsRanges
                .filter { it.lower <= 15 && it.upper >= 30 }
                .minByOrNull { it.lower }

            val noiseReductionModes = cameraInfo.getCameraCharacteristic(
                CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
            ) ?: intArrayOf()
            val options = CaptureRequestOptions.Builder().apply {
                lowLightRange?.let {
                    setCaptureRequestOption(
                        CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
                        Range(it.lower, it.upper),
                    )
                }
                if (noiseReductionModes.contains(CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY)) {
                    setCaptureRequestOption(
                        CaptureRequest.NOISE_REDUCTION_MODE,
                        CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY,
                    )
                }
            }.build()
            Camera2CameraControl.from(boundCamera.cameraControl).setCaptureRequestOptions(options)
        }
    }

    fun takePicture() {
        val capture = imageCapture ?: return onMessage("相机尚未就绪")
        capture.flashMode = ImageCapture.FLASH_MODE_OFF
        camera?.cameraControl?.enableTorch(false)
        onState(CameraState.WORKING)
        val options = ImageCapture.OutputFileOptions.Builder(context.contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaRepository(context).imageValues()).build()
        capture.takePicture(options, executor, object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                onState(CameraState.FREE)
                onMessage("照片已保存到 Download/jf")
            }
            override fun onError(exception: ImageCaptureException) {
                onState(CameraState.ERROR)
                onMessage("拍照失败：${exception.message}")
            }
        })
    }

    fun toggleRecording() {
        recording?.let { it.stop(); return }
        val capture = videoCapture ?: return onMessage("相机尚未就绪")
        camera?.cameraControl?.enableTorch(false)
        val temporaryFile = File.createTempFile("webcam-recording-", ".mp4", context.cacheDir)
        val options = FileOutputOptions.Builder(temporaryFile).build()
        val pending: PendingRecording = capture.output.prepareRecording(context, options)
        onState(CameraState.WORKING)
        recording = pending.start(executor) { event ->
            when (event) {
                is VideoRecordEvent.Start -> onState(CameraState.RECORDING)
                is VideoRecordEvent.Finalize -> {
                    recording = null
                    if (event.hasError()) {
                        temporaryFile.delete()
                        onState(CameraState.ERROR)
                        onMessage("录像失败：${event.error}")
                        afterRecordingStopped?.also { callback -> afterRecordingStopped = null; callback() }
                    } else {
                        onState(CameraState.WORKING)
                        fileExecutor.execute {
                            val saved = MediaRepository(context).importRecordedVideo(temporaryFile)
                            executor.execute {
                                if (saved) { onState(CameraState.FREE); onMessage("视频已保存到 Download/jf") }
                                else { onState(CameraState.ERROR); onMessage("录像保存失败") }
                                afterRecordingStopped?.also { callback -> afterRecordingStopped = null; callback() }
                            }
                        }
                    }
                }
            }
        }
    }

    fun stopRecording() { recording?.stop() }
    fun stopRecording(onStopped: () -> Unit) {
        val activeRecording = recording
        if (activeRecording == null) onStopped()
        else {
            afterRecordingStopped = onStopped
            activeRecording.stop()
        }
    }
    fun release() { recording?.stop(); recording = null; provider?.unbindAll() }
}
