package com.humand.demo.modules.AudioPlayer

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil

class ProximityController(private val reactContext: ReactContext) {
    private val handler = Handler(Looper.getMainLooper())
    private var intervalRunnable: Runnable? = null

    private var isObserving = false
    private var listener: SensorEventListener? = null

    private var lastHasExternal: Boolean? = null

    private val audioManager: AudioManager =
        reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
    private val sensorManager: SensorManager? =
        reactContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager?
    private val powerManager =
        reactContext.getSystemService(Context.POWER_SERVICE) as PowerManager
    private val proximitySensor: Sensor? =
        sensorManager?.getDefaultSensor(Sensor.TYPE_PROXIMITY)

    private val wakeLock: PowerManager.WakeLock by lazy {
        powerManager.newWakeLock(
            PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
            "Humand:ProximityController"
        )
    }

    // Start observing proximity sensor if hasExternalAudioOutput is false
    // start interval to check for bluetooth device connection to stop/start proximity observing if needed
    fun start() {
        startInterval()
        if (!hasExternalAudioOutput()) {
            startProximityObserving()
        }
    }

    // Stop observing proximity sensor and stop interval
    fun stop() {
        stopInterval()
        stopProximityObserving()
    }

    private fun findAudioDeviceByType(type: Int): AudioDeviceInfo? {
        return audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
            .firstOrNull { it.type == type }
    }

    private fun hasExternalAudioOutput(): Boolean {
        val outputs = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
        return outputs.any { dev ->
            when (dev.type) {
                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
                AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
                AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
                AudioDeviceInfo.TYPE_WIRED_HEADSET,
                AudioDeviceInfo.TYPE_USB_HEADSET -> true

                else -> false
            }
        }
    }

    private fun startProximityObserving() {
        if (isObserving) return

        runOnUiThread {
            if (isObserving) return@runOnUiThread
            isObserving = true

            val sensor = proximitySensor ?: return@runOnUiThread

            if (!wakeLock.isHeld) {
                wakeLock.acquire()
            }

            listener = object : SensorEventListener {
                override fun onSensorChanged(event: SensorEvent) {
                    val isNear = event.values[0] < sensor.maximumRange

                    // This is needed to navigate to EARPIECE
                    audioManager.mode =
                        if (isNear) AudioManager.MODE_IN_COMMUNICATION else AudioManager.MODE_NORMAL

                    // Set speakerphone on/off based on proximity sensor value
                    // If isNear is true -> use EARPIECE otherwise -> SPEAKER
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                        val type = if (isNear) {
                            AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
                        } else {
                            AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
                        }

                        val device =
                            findAudioDeviceByType(type)
                        if (device != null) {
                            audioManager.setCommunicationDevice(device)
                        }
                    } else {
                        audioManager.setSpeakerphoneOn(!isNear)
                    }
                }

                override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
            }

            sensorManager?.registerListener(
                listener,
                sensor,
                SensorManager.SENSOR_DELAY_NORMAL
            )
        }
    }

    private fun stopProximityObserving() {
        if (!isObserving) return
        runOnUiThread {
            if (!isObserving) return@runOnUiThread
            isObserving = false
            listener?.let {
                sensorManager?.unregisterListener(it)

            }
            listener = null

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                audioManager.clearCommunicationDevice()
            }
            audioManager.mode = AudioManager.MODE_NORMAL

            if (wakeLock.isHeld) {
                wakeLock.release()
            }
        }
    }

    // Interval to check for bluetooth device connection to stop/start proximity observing if needed
    private fun startInterval() {
        if (intervalRunnable != null) return

        intervalRunnable = object : Runnable {
            override fun run() {
                val hasExternal = hasExternalAudioOutput()

                if (lastHasExternal != hasExternal) {
                    lastHasExternal = hasExternal

                    if (hasExternal) {
                        stopProximityObserving()
                    } else {
                        startProximityObserving()
                    }
                }
                handler.postDelayed(this, 1000)
            }
        }

        handler.post(intervalRunnable!!)
    }

    private fun stopInterval() {
        intervalRunnable?.let {
            handler.removeCallbacks(it)
        }
        intervalRunnable = null
        lastHasExternal = null
    }

    private fun runOnUiThread(action: () -> Unit) {
        if (UiThreadUtil.isOnUiThread()) {
            action()
            return
        }

        UiThreadUtil.runOnUiThread {
            action()
        }
    }
}
