package com.dronex.rec.recording

import android.os.Handler
import android.os.Looper
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.dronex.rec.AppConstants
import com.dronex.rec.MainActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class RecordingPlatformHandler(
        private val activity: MainActivity,
        messenger: BinaryMessenger,
) : MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
    private val methodChannel = MethodChannel(messenger, AppConstants.RECORDING_METHOD_CHANNEL)
    private val eventChannel = EventChannel(messenger, AppConstants.RECORDING_EVENT_CHANNEL)

    private val mainHandler = Handler(Looper.getMainLooper())
    private var eventSink: EventChannel.EventSink? = null
    private var elapsedTicker: Runnable? = null

    private val controller by lazy { RecordingSession.controller(activity.applicationContext) }

    init {
        methodChannel.setMethodCallHandler(this)
        eventChannel.setStreamHandler(this)
        controller.statusListener = { status ->
            mainHandler.post { eventSink?.success(status.toMap()) }
        }
    }

    fun dispose() {
        stopElapsedTicker()
        methodChannel.setMethodCallHandler(null)
        eventChannel.setStreamHandler(null)
        controller.statusListener = null
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "initializePreview" -> initializePreview(result)
            "startRecording" -> {
                val withAudio = call.argument<Boolean>("withAudio") ?: true
                val enableDnd = call.argument<Boolean>("enableDoNotDisturb") ?: true
                val displayName = call.argument<String>("displayName")
                startRecording(withAudio, enableDnd, displayName, result)
            }
            "stopRecording" -> stopRecording(result)
            "getZoomCapabilities" -> result.success(controller.zoomCapabilitiesMap())
            "setZoomRatio" -> {
                val ratio = call.argument<Double>("zoomRatio") ?: 1.0
                setZoomRatio(ratio, result)
            }
            "disposePreview" -> {
                controller.unbind()
                result.success(null)
            }
            "hasNotificationPolicyAccess" -> result.success(DoNotDisturbHelper.hasAccess(activity))
            "openNotificationPolicySettings" -> {
                DoNotDisturbHelper.openAccessSettings(activity)
                result.success(null)
            }
            "enableDoNotDisturb" -> result.success(DoNotDisturbHelper.enable(activity))
            "disableDoNotDisturb" -> {
                DoNotDisturbHelper.disable(activity)
                result.success(null)
            }
            "isIgnoringBatteryOptimizations" ->
                    result.success(BatteryOptimizationHelper.isIgnoringOptimizations(activity))
            "openBatteryOptimizationSettings" -> {
                BatteryOptimizationHelper.openSettings(activity)
                result.success(null)
            }
            "setImmersiveMode" -> {
                val enabled = call.argument<Boolean>("enabled") ?: false
                setImmersiveMode(enabled)
                result.success(null)
            }
            "getStatus" -> result.success(controller.status.toMap())
            "isForegroundServiceRunning" -> result.success(RecordingForegroundService.isRunning)
            else -> result.notImplemented()
        }
    }

    private fun initializePreview(result: MethodChannel.Result) {
        val previewView = activity.recordingPreviewView
        if (previewView == null) {
            result.error("NO_PREVIEW", "Camera preview is not attached", null)
            return
        }

        controller.bindPreview(activity, previewView) { ready ->
            mainHandler.post {
                if (ready) {
                    result.success(controller.status.toMap())
                } else {
                    result.error("PREVIEW_FAILED", "Failed to bind camera preview", null)
                }
            }
        }
    }

    private fun startRecording(
            withAudio: Boolean,
            enableDnd: Boolean,
            displayName: String?,
            result: MethodChannel.Result,
    ) {
        val previewView = activity.recordingPreviewView
        if (previewView == null) {
            result.error("NO_PREVIEW", "Camera preview is not attached", null)
            return
        }

        RecordingSession.startForeground(activity)

        fun beginCapture() {
            if (enableDnd && DoNotDisturbHelper.hasAccess(activity)) {
                DoNotDisturbHelper.enable(activity)
            }

            controller.startRecording(withAudio, displayName) { started, message ->
                mainHandler.post {
                    if (started) {
                        startElapsedTicker()
                        result.success(
                                mapOf(
                                        "outputPath" to message,
                                        "status" to controller.status.toMap(),
                                ),
                        )
                    } else {
                        RecordingSession.stopForeground(activity)
                        DoNotDisturbHelper.disable(activity)
                        result.error("START_FAILED", message, null)
                    }
                }
            }
        }

        fun rebindAndCapture() {
            val lifecycleOwner = RecordingForegroundService.instance ?: activity
            controller.rebindForRecording(lifecycleOwner, previewView) { ready ->
                if (ready) {
                    beginCapture()
                } else {
                    RecordingSession.stopForeground(activity)
                    result.error("REBIND_FAILED", "Failed to bind camera for recording", null)
                }
            }
        }

        if (RecordingForegroundService.instance != null) {
            rebindAndCapture()
        } else {
            mainHandler.post { rebindAndCapture() }
        }
    }

    private fun stopRecording(result: MethodChannel.Result) {
        stopElapsedTicker()
        controller.stopRecording { path ->
            RecordingSession.stopForeground(activity)
            DoNotDisturbHelper.disable(activity)
            val previewView = activity.recordingPreviewView
            if (previewView == null) {
                mainHandler.post { deliverStopResult(result, path) }
                return@stopRecording
            }
            controller.rebindForRecording(activity, previewView) { _ ->
                mainHandler.post { deliverStopResult(result, path) }
            }
        }
    }

    private fun setZoomRatio(ratio: Double, result: MethodChannel.Result) {
        controller.setZoomRatio(ratio) { success, capabilities, message ->
            mainHandler.post {
                if (success) {
                    result.success(capabilities)
                } else {
                    result.error("ZOOM_FAILED", message ?: "Failed to set camera zoom", null)
                }
            }
        }
    }

    private fun deliverStopResult(result: MethodChannel.Result, path: String?) {
        val gallerySaved = path != null && controller.status.state != RecordingState.ERROR
        val payload =
                mutableMapOf<String, Any?>(
                        "outputPath" to path,
                        "status" to controller.status.toMap(),
                        "gallerySaved" to gallerySaved,
                )
        if (!gallerySaved) {
            payload["galleryErrorMessage"] = controller.status.message ?: "保存到相册失败"
        }
        result.success(payload)
    }

    private fun setImmersiveMode(enabled: Boolean) {
        val window = activity.window
        WindowCompat.setDecorFitsSystemWindows(window, !enabled)
        val insetsController = WindowInsetsControllerCompat(window, window.decorView)
        if (enabled) {
            insetsController.hide(WindowInsetsCompat.Type.systemBars())
            insetsController.systemBarsBehavior =
                    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
        } else {
            insetsController.show(WindowInsetsCompat.Type.systemBars())
        }
    }

    private fun startElapsedTicker() {
        stopElapsedTicker()
        elapsedTicker =
                object : Runnable {
                            override fun run() {
                                if (controller.status.state == RecordingState.RECORDING) {
                                    eventSink?.success(
                                            controller
                                                    .status
                                                    .copy(
                                                            elapsedMillis =
                                                                    controller.elapsedMillis(),
                                                    )
                                                    .toMap(),
                                    )
                                    mainHandler.postDelayed(this, 1000L)
                                }
                            }
                        }
                        .also { mainHandler.post(it) }
    }

    private fun stopElapsedTicker() {
        elapsedTicker?.let { mainHandler.removeCallbacks(it) }
        elapsedTicker = null
    }

    override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
        eventSink = events
        events?.success(controller.status.toMap())
    }

    override fun onCancel(arguments: Any?) {
        eventSink = null
        stopElapsedTicker()
    }
}
