package kr.co.vividnext.sodalive.message.voice

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Dialog
import android.content.Intent
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import androidx.core.content.res.ResourcesCompat
import coil.load
import coil.transform.RoundedCornersTransformation
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.normal.TedPermission
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.FragmentVoiceMessageWriteBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
import kr.co.vividnext.sodalive.message.SelectMessageRecipientActivity
import org.koin.android.ext.android.inject
import java.io.File
import java.io.IOException

class VoiceMessageWriteFragment(
    private val onSendSuccess: () -> Unit,
    private val senderId: Long? = null,
    private val senderNickname: String? = null,
    private val senderProfileUrl: String? = null
) : BottomSheetDialogFragment() {

    private val viewModel: VoiceMessageWriteViewModel by inject()

    private lateinit var loadingDialog: LoadingDialog
    private lateinit var binding: FragmentVoiceMessageWriteBinding
    private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>

    private var countDownTimer: CountDownTimer? = null
    private var mediaRecorder: MediaRecorder? = null
    private var mediaPlayer: MediaPlayer? = null
    private var fileNameMedia = ""

    private var second = -1
    private var minute = 0
    private var hour = 0

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityResultLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()
        ) {
            if (it.resultCode == Activity.RESULT_OK) {
                if (it.data != null) {
                    val recipient = IntentCompat.getParcelableExtra(
                        it.data!!,
                        Constants.EXTRA_SELECT_RECIPIENT,
                        GetRoomDetailUser::class.java
                    )

                    if (recipient != null) {
                        setReceiver(
                            userId = recipient.id,
                            nickname = recipient.nickname,
                            profileUrl = recipient.profileImageUrl
                        )
                    }
                }
            }
        }

        TedPermission.create()
            .setPermissionListener(object : PermissionListener {
                override fun onPermissionGranted() {
                }

                override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
                    dismiss()
                }
            })
            .setDeniedMessage("오디오 녹음 권한을 거부하시면 음성 메시지를 이용하실 수 없습니다.")
            .setPermissions(Manifest.permission.RECORD_AUDIO)
            .check()
    }

    private fun setReceiver(userId: Long, nickname: String, profileUrl: String) {
        binding.ivProfile.load(profileUrl) {
            crossfade(true)
            placeholder(R.drawable.ic_place_holder)
            transformations(RoundedCornersTransformation(23.4f.dpToPx()))
        }
        binding.tvNickname.text = nickname
        binding.tvNickname.typeface = ResourcesCompat.getFont(
            requireContext(),
            R.font.gmarket_sans_bold
        )
        binding.tvNickname.setTextColor(
            ContextCompat.getColor(
                requireContext(),
                R.color.color_eeeeee
            )
        )
        binding.ivPlus.visibility = View.GONE
        viewModel.recipientId = userId
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState)

        dialog.setOnShowListener {
            val d = it as BottomSheetDialog
            val bottomSheet = d.findViewById<FrameLayout>(
                com.google.android.material.R.id.design_bottom_sheet
            )
            if (bottomSheet != null) {
                BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
            }
        }

        return dialog
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentVoiceMessageWriteBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        loadingDialog = LoadingDialog(requireActivity(), layoutInflater)

        bindData()

        binding.ivClose.setOnClickListener { dismiss() }

        if (senderId != null && senderProfileUrl != null && senderNickname != null) {
            setReceiver(
                userId = senderId,
                nickname = senderNickname,
                profileUrl = senderProfileUrl
            )
        } else {
            binding.rlSelectRecipient.setOnClickListener {
                val intent = Intent(requireContext(), SelectMessageRecipientActivity::class.java)
                activityResultLauncher.launch(intent)
            }
        }

        binding.ivRecordStart.setOnClickListener {
            if (viewModel.recipientId <= 0) {
                Toast.makeText(
                    requireContext(),
                    "받는 사람을 선택해 주세요.",
                    Toast.LENGTH_LONG
                ).show()

                return@setOnClickListener
            }

            fileNameMedia =
                "${requireActivity().filesDir.path}/voice_message_${System.currentTimeMillis()}.mp3"

            val fileMedia = File(fileNameMedia)
            if (!fileMedia.exists()) {
                try {
                    fileMedia.createNewFile()

                    startRecording()
                } catch (e: IOException) {
                    Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show()
                    e.printStackTrace()
                }
            }
        }

        binding.ivRecordStop.setOnClickListener {
            stopRecording()
        }

        binding.ivRecordPlay.setOnClickListener {
            startPlaying()
        }

        binding.ivRecordPause.setOnClickListener {
            stopPlaying()
        }

        binding.tvDelete.setOnClickListener {
            if (fileNameMedia.isNotBlank()) {
                val fileMedia = File(fileNameMedia)
                if (fileMedia.exists()) {
                    fileMedia.delete()
                }
                fileNameMedia = ""
            }

            binding.ivRecordStart.visibility = View.VISIBLE
            binding.llRetryOrSend.visibility = View.GONE
            binding.rlRecordPlay.visibility = View.GONE
            binding.soundVisualizer.visibility = View.GONE
        }

        binding.tvRetryRecord.setOnClickListener {
            if (fileNameMedia.isNotBlank()) {
                val fileMedia = File(fileNameMedia)
                if (fileMedia.exists()) {
                    fileMedia.delete()
                }
                fileNameMedia = ""
            }

            binding.ivRecordStart.visibility = View.VISIBLE
            binding.llRetryOrSend.visibility = View.GONE
            binding.rlRecordPlay.visibility = View.GONE
            binding.soundVisualizer.visibility = View.GONE
        }

        binding.tvSendMessage.setOnClickListener {
            viewModel.write(File(fileNameMedia)) {
                dismiss()
                onSendSuccess()
            }
        }
    }

    private fun bindData() {
        viewModel.toastLiveData.observe(viewLifecycleOwner) {
            it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
        }

        viewModel.isLoading.observe(this) {
            if (it) {
                loadingDialog.show(resources.displayMetrics.widthPixels)
            } else {
                loadingDialog.dismiss()
            }
        }
    }

    override fun onDestroy() {
        if (mediaPlayer != null) {
            mediaPlayer!!.release()
            mediaPlayer = null
        }

        if (mediaRecorder != null) {
            // stop recording and free up resources
            mediaRecorder!!.stop()
            mediaRecorder!!.reset()
            mediaRecorder!!.release()

            mediaRecorder = null
        }

        if (fileNameMedia.isNotBlank()) {
            val fileMedia = File(fileNameMedia)
            if (fileMedia.exists()) {
                fileMedia.delete()
            }

            fileNameMedia = ""
        }

        super.onDestroy()
    }

    private fun startRecording() {
        if (mediaRecorder == null) {
            // safety check, don't start a new recording if one is already going
            mediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                MediaRecorder(requireContext())
            } else {
                MediaRecorder()
            }
            mediaRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
            mediaRecorder!!.setOutputFile(fileNameMedia)
            mediaRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            mediaRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)

            try {
                mediaRecorder!!.prepare()
            } catch (e: Exception) {
                Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show()
                return
            }

            mediaRecorder!!.start()
            binding.ivRecordStart.visibility = View.GONE
            binding.ivRecordStop.visibility = View.VISIBLE

            startCountDownTimer()
        }
    }

    private fun stopRecording() {
        if (mediaRecorder != null) {
            // stop recording and free up resources
            mediaRecorder!!.stop()
            mediaRecorder!!.reset()
            mediaRecorder!!.release()

            mediaRecorder = null

            binding.ivRecordStop.visibility = View.GONE
            binding.rlRecordPlay.visibility = View.VISIBLE
            binding.llRetryOrSend.visibility = View.VISIBLE

            stopCountDownTimer()
        }
    }

    private fun startPlaying() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer()
            mediaPlayer!!.reset()

            mediaPlayer!!.setOnCompletionListener {
                if (mediaPlayer != null) {
                    mediaPlayer!!.release()
                    mediaPlayer = null
                }
                binding.tvDelete.visibility = View.VISIBLE
                binding.ivRecordPlay.visibility = View.VISIBLE
                binding.llRetryOrSend.visibility = View.VISIBLE
                binding.ivRecordPause.visibility = View.GONE
                binding.soundVisualizer.visibility = View.GONE

                stopCountDownTimer()
            }

            mediaPlayer!!.setOnPreparedListener {
                binding.soundVisualizer.visibility = View.VISIBLE
                binding.soundVisualizer.setAudioSessionId(mediaPlayer!!.audioSessionId)
                it.start()

                startCountDownTimer()
            }

            try {
                mediaPlayer!!.setDataSource(fileNameMedia)
                mediaPlayer!!.prepare()
            } catch (e: Exception) {
                Toast.makeText(requireActivity(), R.string.retry, Toast.LENGTH_LONG).show()
                return
            }

            binding.tvDelete.visibility = View.GONE
            binding.ivRecordPlay.visibility = View.GONE
            binding.llRetryOrSend.visibility = View.GONE
            binding.ivRecordPause.visibility = View.VISIBLE
        }
    }

    private fun stopPlaying() {
        if (mediaPlayer != null) {
            mediaPlayer!!.release()
            mediaPlayer = null
        }

        binding.tvDelete.visibility = View.VISIBLE
        binding.ivRecordPlay.visibility = View.VISIBLE
        binding.llRetryOrSend.visibility = View.VISIBLE
        binding.ivRecordPause.visibility = View.GONE
        binding.soundVisualizer.visibility = View.GONE

        stopCountDownTimer()
    }

    private fun startCountDownTimer() {
        countDownTimer = object : CountDownTimer(Long.MAX_VALUE, 1000) {
            override fun onTick(p0: Long) {
                second += 1
                binding.tvTimer.text = recordingTime()
            }

            override fun onFinish() {
            }
        }

        countDownTimer!!.start()
    }

    private fun recordingTime(): String {
        if (second == 60) {
            minute += 1
            second = 0
        }

        if (minute == 60) {
            hour += 1
            minute = 0
        }

        return String.format("%02d:%02d:%02d", hour, minute, second)
    }

    @SuppressLint("SetTextI18n")
    private fun stopCountDownTimer() {
        if (countDownTimer != null) {
            countDownTimer!!.cancel()
            countDownTimer = null
        }

        binding.tvTimer.text = "00:00:00"
        second = -1
        minute = 0
        hour = 0
    }
}
