package xn.xxp.home.sign

import android.content.Context
import android.os.Bundle
import android.os.Message
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
import core.ui.fragment.RcBaseFragment
import core.util.MediaUtils
import core.util.OnWeakListener
import core.util.WeakHandler
import core.util.ifNullOrEmpty
import http.client.ApiRepository
import http.exception.AICheckException
import http.exception.NetException
import http.vo.request.CheckInAllReq
import http.vo.request.PatrolSignInReq
import http.vo.response.LaboratoryVo
import io.fotoapparat.Fotoapparat
import io.fotoapparat.facedetector.processor.FaceDetectorProcessor
import io.fotoapparat.log.fileLogger
import io.fotoapparat.log.logcat
import io.fotoapparat.log.loggers
import io.fotoapparat.selector.front
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.greenrobot.eventbus.EventBus
import xn.xxp.R
import xn.xxp.app.LabApp
import xn.xxp.databinding.FragmentSafetyCheckBinding
import xn.xxp.home.auth.AuthType
import xn.xxp.home.auth.SignType
import xn.xxp.home.auth.fragment.CardAuthFragment
import xn.xxp.mqtt.event.OnlineUserEvent
import xn.xxp.room.RoomTool
import xn.xxp.room.bean.DeviceConfig
import xn.xxp.room.bean.LabConfig
import xn.xxp.utils.AudioPlayer
import xn.xxp.utils.Tool
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.concurrent.TimeUnit

/**
 * 安全准入检测
 *
 * @author ReiChin_
 */
class SafetyCheckFragment :
    RcBaseFragment<FragmentSafetyCheckBinding>(),
    OnWeakListener {

    companion object {
        // 5s后，开始拍照/录像
        private const val MAX_TIME = 5

        private const val WHAT_REPEAT = 1
        private const val WHAT_TIME_OUT = 2
        private const val WHAT_RECORDING = 3
        private const val WHAT_COUNT_DOWN = 4

        private const val REPEAT_TIME = 10 * 1000L
        private const val TIME_OUT = 30 * 1000L
        private const val RECORDING_HEART = 300L

        fun newInstance(id: String): SafetyCheckFragment {
            val args = Bundle()
            args.putString("id", id)
            val fragment = SafetyCheckFragment()
            fragment.arguments = args
            return fragment
        }

    }

    private var mCounter = 0

    private var mCurrentCheckItem: CheckItem? = null
    private var mSignTyp = SignType.ACCESS.code

    private var mJumpSafetyCheck = false

    private var isMatching = false

    private var mSignId: String? = null

    private lateinit var laboratoryVo: LaboratoryVo
    private lateinit var labConfig: LabConfig
    private lateinit var deviceConfig: DeviceConfig

    private val viewModel by lazy {
        ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory()).get(
            SignViewModel::class.java
        )
    }

    private var mFragmentCallback: SignInFragmentCallback? = null
    private var mCountDown: ICountDown? = null

    private lateinit var mFotoapparat: Fotoapparat

    override fun onAttach(context: Context) {
        super.onAttach(context)
        mFragmentCallback = context as? SignInFragmentCallback
        mCountDown = context as? ICountDown
    }

    override fun onDetach() {
        super.onDetach()
        mFragmentCallback = null
        mCountDown = null
    }

    private fun takePicture() {
        isMatching = true
        val format = SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault())
        val fileName = "${format.format(Calendar.getInstance().time)}.jpg"
        val photoFile = File(requireActivity().getExternalFilesDir("photos"), fileName)
        mFotoapparat.takePicture()
            .saveToFile(photoFile)
            .whenAvailable {
                handlerTakePictureResult(photoFile)
            }
    }

    override fun onResume() {
        super.onResume()
        try {
            mFotoapparat.stop()
        } catch (e: Exception) {
        }
        viewBinding.cameraView.postDelayed({
            mFotoapparat.start()
            isMatching = false
        }, 500)
    }

    override fun onPause() {
        super.onPause()
        mFotoapparat.stop()
    }

    override fun createViewBinding(
        inflater: LayoutInflater,
        container: ViewGroup?
    ) = FragmentSafetyCheckBinding.inflate(inflater, container, false)

    override fun initViews(savedInstanceState: Bundle?) {
        laboratoryVo = LabApp.laboratory
        labConfig = RoomTool.getInstance().labConfigDao().labConfig
        deviceConfig = RoomTool.getInstance().deviceConfigDao().deviceConfig
        mSignId = arguments?.getString("id")
        viewModel.checkItemData.observe(viewLifecycleOwner) { data ->
            mCurrentCheckItem = data
            data?.let {
                preStartCheck()
            }
        }
        viewModel.signTypeData.observe(viewLifecycleOwner) { data ->
            mSignTyp = data ?: SignType.ACCESS.code
        }
        viewModel.signFaceData.observe(viewLifecycleOwner) { data ->
            data?.let {
                mSignId = data.id
                mCurrentCheckItem = CheckItem(data.code)
                preStartCheck()
            }
        }
        mFotoapparat = Fotoapparat.with(requireActivity())
            .into(viewBinding.cameraView)
            .lensPosition(
                front()
            )//front（）前置  back 后置
            .frameProcessor(
                FaceDetectorProcessor.with(requireActivity())
                    .listener { faces ->
                        viewBinding.rectanglesView.setRectangles(faces)
                    }.build()
            )
            .cameraErrorCallback { onError(it.message) }
            .logger(loggers(logcat(), fileLogger(requireActivity())))
            .build()
         // 👇 加这一行：旋转预览视图，解决前置摄像头上下颠倒
         viewBinding.cameraView.rotation = 180f

        // 开始检测
        viewBinding.startCheck.setOnClickListener {
            // 移除语音播报提示检测Message
            mCountDown?.onStopCountDown()
            mWeakHandler.removeMessages(WHAT_REPEAT)
            mWeakHandler.removeMessages(WHAT_TIME_OUT)

            // 按钮不可用
            viewBinding.startCheck.isEnabled = false
            // 倒计时5s，结束后，拍照开始
            mCounter = 0
            viewBinding.countDown.visibility = View.VISIBLE
            mWeakHandler.sendEmptyMessage(WHAT_COUNT_DOWN)
        }

        // 电子牌 + 巡查签到
        if (1 == labConfig.isStart && SignType.PATROL.code == mSignTyp) {
            preStartCheck()
        }
//        takePictureInterval()
    }

    private fun preStartCheck() {
        mCountDown?.onStartCountDown()
        dispatchCheckTip()
        mWeakHandler.sendEmptyMessageDelayed(WHAT_TIME_OUT, TIME_OUT)
    }

    private fun takePictureInterval() {
        val disposable = io.reactivex.rxjava3.core.Observable.interval(3, 3, TimeUnit.SECONDS)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe {
                if (isVisible && !isMatching) takePicture()
            }
        addDisposable(disposable)
    }

    private val mWeakHandler by lazy { WeakHandler(this) }

    override fun onWeakHandleMessage(msg: Message) {
        if (!isVisible) return

        when (msg.what) {
            WHAT_COUNT_DOWN -> handleCountDown(msg)
            WHAT_RECORDING -> handleRecording(msg)
            WHAT_REPEAT -> dispatchCheckTip()
            WHAT_TIME_OUT -> dispatchTimeOut()
        }
    }

    private fun handleCountDown(msg: Message) {
        val countDown = MAX_TIME - mCounter
        if (countDown <= 0) {
            viewBinding.countDown.visibility = View.GONE
            // 倒计时结束，开始拍照
            takePicture()
        } else {
            viewBinding.countDown.text = "$countDown"
            msg.target.sendEmptyMessageDelayed(WHAT_COUNT_DOWN, 1000)
        }
        mCounter++
    }

    private fun handleRecording(msg: Message) {
        val visibility = msg.obj as? Boolean ?: false
        viewBinding.recording.visibility =
            if (visibility) View.VISIBLE else View.INVISIBLE

        val message = msg.target.obtainMessage(WHAT_RECORDING, !visibility)
        msg.target.sendMessageDelayed(message, RECORDING_HEART)
    }

    private fun dispatchCheckTip() {
//        if (!isResumed) return
        val message = "穿戴安全准入检测，请确认处于检测区域后，点击开始检测。"
        AudioPlayer.getInstance().play(R.raw.audio_check_ready)
        mWeakHandler.sendEmptyMessageDelayed(WHAT_REPEAT, REPEAT_TIME)
    }

    private fun dispatchTimeOut() {
        mCountDown?.onStopCountDown()
        AudioPlayer.getInstance().play(R.raw.audio_failed_sign)
        SafetyCheckResultDialog(requireActivity(), ResultEnum.FAIL, "签到失败", 3000).apply {
            setCancelable(false)
            setOnDismissListener { requireActivity().finish() }
            show()
        }
    }

    private fun handlerTakePictureResult(photoFile: File) {
        if (!isVisible) return
        viewBinding.startCheck.isEnabled = true

        // 1) 显示loading
        val message = "检测中，请勿操作屏幕。"
        AudioPlayer.getInstance().play(R.raw.audio_checking)
        if (null == mCheckLoading) {
            mCheckLoading =
                SafetyCheckResultDialog(requireActivity(), ResultEnum.LOADING, message).apply {
                    setCancelable(false)
                }
        }
        mCheckLoading?.show()

        // 2) 调用后台接口：穿戴安全准入检测
        callCheckApi(photoFile)
    }

    /*
     * 调用检测接口
     */
    private fun callCheckApi(photoFile: File) {

        if (null == labConfig || null == laboratoryVo) {
            showToast("暂无实验室信息，即将重新获取")
            Tool.INSTANCE.reStartApp("暂无实验室信息，即将重新获取")
            return
        }
        val pram = CheckInAllReq().apply {
            subId = labConfig.labId.toString()
            subName = laboratoryVo.subName
            id = mSignId
            file = photoFile
        }
        val disposable = ApiRepository.checkInAll(SignType.PATROL.code == mSignTyp, pram)
            .subscribe({
                // 删除源文件
                MediaUtils.deleteFile(photoFile)
                // 检测通过：跳转到下一级检测
                dismissCheckLoadingDialog()
                dispatchCheckSuccess()
            }, { throwable ->
                // 删除源文件
                MediaUtils.deleteFile(photoFile)
                // 检测失败：弹出提示，重新检测
                dismissCheckLoadingDialog()
                if (!pretreatmentError(throwable, "穿戴检测")) {
                    throwable.printStackTrace()
                    showNetError(throwable)

                    // 出现其它错误时，重新开始计时。
                    preStartCheck()
                }
            })
        addDisposable(disposable)
    }

    private fun dismissCheckLoadingDialog() {
        mCheckLoading?.let {
            if (it.isShowing) it.dismiss()
            mCheckLoading = null
        }
    }

    private fun dispatchCheckSuccess() {
        val message = "穿戴安全准入检测完成。"
        val dialog = SafetyCheckResultDialog(
            requireActivity(),
            ResultEnum.SUCCESS,
            message
        ).apply {
            setCancelable(false)
            show()
        }
        AudioPlayer.getInstance().play(R.raw.audio_wear_check_completed) {
            dialog.dismiss()
            callSignInSubmitApi()
        }
    }

    private fun callSignInSubmitApi() {
        showLoading("签到中...")
        val isPatrol = SignType.PATROL.code == mSignTyp
        val disposable = if (isPatrol) {
            val param = PatrolSignInReq().apply {
                num = deviceConfig.devId
                userId = LabApp.userVo.userId
                subjectId = labConfig.labId.toString()
            }
            ApiRepository.signInWithPatrol(param)
        } else {
            val code = mCurrentCheckItem?.code ?: ""
            ApiRepository.signIn(mJumpSafetyCheck, code, mSignId!!)
        }
            .subscribe({ success ->
                dismissLoading()
                if (success) {
                    EventBus.getDefault().post(OnlineUserEvent())
                    val message = "签到成功"
                    AudioPlayer.getInstance().play(R.raw.audio_success_sign)
                    SafetyCheckResultDialog(
                        requireActivity(),
                        ResultEnum.SUCCESS,
                        message,
                        3000
                    ).apply {
                        setCancelable(false)
                        setOnDismissListener {
                            Tool.INSTANCE.openDoor(LabApp.userVo.userId, AuthType.FACE)
                            requireActivity().finish()
                        }
                        show()
                    }
                }
            }, { throwable ->
                dismissLoading()
                if (!pretreatmentSignInError(throwable)) {
                    throwable.printStackTrace()
                    showNetError(throwable)
                }
            })
        addDisposable(disposable)
    }

    private fun pretreatmentSignInError(throwable: Throwable): Boolean {
        if (throwable is NetException) {
            // 600- 签到&签出已超时，请重新刷卡重试！
            if ("600" == throwable.code) {
                dispatchError_600(throwable.message)
                return true
            } else {
                // 后台返回的其它errorCode
                val message = throwable.message.ifNullOrEmpty("签到失败，请联系实验室管理员。")
                AudioPlayer.getInstance().play(R.raw.audio_failed_sign_admin)
                SafetyCheckResultDialog(requireActivity(), ResultEnum.FAIL, message, 3000).apply {
                    setCancelable(false)
                    setOnDismissListener { requireActivity().finish() }
                    show()
                }
                return true
            }
        }
        return false
    }

    private fun dispatchError_600(message: String?) {
        AlertDialog.Builder(requireActivity())
            .setTitle("提示")
            .setMessage(message)
            .setPositiveButton("确定") { _, _ ->
                val fragment =
                    requireFragmentManager().findFragmentByTag(CardAuthFragment::class.java.simpleName)
                            as? CardAuthFragment ?: CardAuthFragment()
                mFragmentCallback?.switchFragment(fragment)
            }.show()
    }

    private fun pretreatmentError(throwable: Throwable, text: String): Boolean {
        if (throwable is AICheckException) {
            // 600- 签到&签出已超时，请重新刷卡重试！
            if ("600" == throwable.code) {
                dispatchError_600(throwable.message)
                return true
            } else if ("700" == throwable.code) {
                // 700- 检测失败，但是可以跳过此检测项
                mJumpSafetyCheck = true
                dispatchCheckSuccess()
                return true
            } else {
                // 后台返回了其它code
                val front = if ("300" == throwable.code) decodeCheckCode(throwable.data) else null
                val message = "${front ?: ""}穿戴检测未通过，请再次点击开始检测。"
                AudioPlayer.getInstance().play(speakCheckFailed(throwable.data))
                SafetyCheckResultDialog(requireActivity(), ResultEnum.WARNING, message).show()
                return true
            }
        } else if (throwable is NetException) {
            val message = throwable.message.ifNullOrEmpty("${text}失败，请联系实验室管理员。")
            //AudioPlayerManager.speakVoice(R.raw.audio_failed_sign_admin)
            SafetyCheckResultDialog(requireActivity(), ResultEnum.FAIL, message, 3000).apply {
                setCancelable(false)
                show()
            }
            return true
        }
        return false
    }

    private fun speakCheckFailed(code: String?): Int {
        return when (code) {
            "1" -> R.raw.audio_wear_check_hmj_failed
            "2" -> R.raw.audio_wear_check_syf_failed
            "3" -> R.raw.audio_wear_check_st_failed
            else -> R.raw.audio_wear_check_failed
        }
    }

    private fun decodeCheckCode(code: String?): String? {
        val laboratoryVo = LabApp.laboratory
        if (null == laboratoryVo || laboratoryVo.inCheck.isNullOrEmpty()) return null
        return laboratoryVo.inCheck.find { it.code == code }?.name
    }

    private var mCheckLoading: SafetyCheckResultDialog? = null

    private fun onError(message: String?) {
        dismissLoading()
        message?.let { showToast(it) }
        viewBinding.startCheck.isEnabled = true

        // 拍照错误时，重新开始计时。
        mCountDown?.onStartCountDown()
        dispatchCheckTip()
        mWeakHandler.sendEmptyMessageDelayed(WHAT_TIME_OUT, TIME_OUT)
    }

    override fun onDestroyView() {
        mCountDown?.onStopCountDown()
        mWeakHandler.removeCallbacksAndMessages(null)
        super.onDestroyView()
    }

}