package com.seven.ikar.kotlin.feature.chat

import android.content.Context
import android.media.MediaRecorder
import android.os.Build
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.io.File
import java.util.concurrent.Semaphore

class AndroidVoiceNoteRecorderService(
    private val context: Context
) {
    private val sync = Semaphore(1)
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val _elapsedMs = MutableStateFlow(0L)
    val elapsedMs: StateFlow<Long> = _elapsedMs.asStateFlow()

    private var recorder: MediaRecorder? = null
    private var currentFile: File? = null
    private var startedAtMs: Long = 0L
    private var tickerJob: Job? = null

    val isRecording: Boolean
        get() = recorder != null

    suspend fun start() {
        sync.acquire()
        try {
            if (recorder != null) {
                return
            }

            val directory = File(context.cacheDir, "voice-notes").apply { mkdirs() }
            val file = File(directory, "voice-${System.currentTimeMillis()}.m4a")
            val nextRecorder = if (Build.VERSION.SDK_INT >= 31) {
                MediaRecorder(context)
            } else {
                MediaRecorder()
            }

            try {
                nextRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
                nextRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
                nextRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
                nextRecorder.setAudioSamplingRate(44_100)
                nextRecorder.setAudioEncodingBitRate(128_000)
                nextRecorder.setOutputFile(file.absolutePath)
                nextRecorder.prepare()
                nextRecorder.start()
            } catch (error: Throwable) {
                nextRecorder.reset()
                nextRecorder.release()
                file.delete()
                throw error
            }

            recorder = nextRecorder
            currentFile = file
            startedAtMs = System.currentTimeMillis()
            _elapsedMs.value = 0L
            startTicker()
        } finally {
            sync.release()
        }
    }

    suspend fun stop(discard: Boolean): File? {
        sync.acquire()
        val currentRecorder = recorder
        val outputFile = currentFile
        val elapsedMs = _elapsedMs.value
        recorder = null
        currentFile = null
        startedAtMs = 0L
        sync.release()

        stopTicker()

        if (currentRecorder == null) {
            _elapsedMs.value = 0L
            return null
        }

        var shouldDelete = discard
        try {
            currentRecorder.stop()
        } catch (_: Throwable) {
            shouldDelete = true
        } finally {
            currentRecorder.reset()
            currentRecorder.release()
        }

        _elapsedMs.value = 0L

        if (outputFile == null || !outputFile.exists()) {
            return null
        }

        if (shouldDelete) {
            outputFile.delete()
            return null
        }

        if (outputFile.length() <= 0) {
            outputFile.delete()
            return null
        }

        if (elapsedMs < 600L) {
            outputFile.delete()
            throw IllegalStateException("Голосовое сообщение слишком короткое.")
        }

        return outputFile
    }

    private fun startTicker() {
        stopTicker()
        tickerJob = scope.launch {
            try {
                while (true) {
                    delay(250)
                    if (recorder == null) {
                        break
                    }

                    _elapsedMs.value = (System.currentTimeMillis() - startedAtMs).coerceAtLeast(0L)
                }
            } catch (_: CancellationException) {
            }
        }
    }

    private fun stopTicker() {
        tickerJob?.cancel()
        tickerJob = null
    }
}
