package com.doverfuelingsolutions.issp.fusion

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import com.doverfuelingsolutions.issp.R
import com.doverfuelingsolutions.issp.api.FuelInfoApi
import com.doverfuelingsolutions.issp.api.dto.DFSResult
import com.doverfuelingsolutions.issp.api.entity.PumpInfo
import com.doverfuelingsolutions.issp.data.GlobalData
import com.doverfuelingsolutions.issp.fusion.callback.OnFusionEvent
import com.doverfuelingsolutions.issp.utils.StringUtil
import com.doverfuelingsolutions.issp.utils.thread.ThreadUtil
import com.doverfuelingsolutions.issp.utils.ValidateUtil
import com.doverfuelingsolutions.issp.utils.log.DFSLog
import com.doverfuelingsolutions.issp.utils.sp.SPKeys
import com.doverfuelingsolutions.issp.utils.sp.SPUtil
import com.wayne.www.waynelib.fdc.FdcClient
import com.wayne.www.waynelib.fdc.OnFdcClientStateChangedListener
import com.wayne.www.waynelib.fdc.OnFdcMessageReceivedListener
import com.wayne.www.waynelib.fdc.OnFdcServiceResponseReceivedListener
import com.wayne.www.waynelib.fdc.message.*
import kotlinx.coroutines.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

object FusionManager : LifecycleObserver, OnFdcClientStateChangedListener,
    OnFdcServiceResponseReceivedListener,
    OnFdcMessageReceivedListener {

    var stateFusion: FdcClient.FdcClientState = FdcClient.FdcClientState.Stopped
    private const val timeoutMax = 4000
    var firstLink = false
        private set
    private var isLogin = false
    val pumpList = arrayListOf<PumpInfo>()

    private val coroutineIO = CoroutineScope(Dispatchers.IO)

    var onFusionEvent: OnFusionEvent? = null

    override fun onFdcClientStateChanged(sender: FdcClient?, state: FdcClient.FdcClientState?) {
        DFSLog.i("Fusion: state = ${state?.name}")
        if (sender == null || state == null) return

        stateFusion = state
        when (state) {
            FdcClient.FdcClientState.Connected -> {
                firstLink = true
                loginFetchInfo()
            }
            // 中途断网重连
            FdcClient.FdcClientState.Connecting -> onFusionEvent?.onFusionReconnect()
            FdcClient.FdcClientState.MyAddReConnect -> {
                if (!firstLink) {
                    onFusionEvent?.onFusionInit(FusionError.WrongAddress, StringUtil.get(R.string.wrong_fusion_address))
                } else {
                    onFusionEvent?.onFusionReconnect()
                }
            }
            else -> {}
        }
    }

    override fun onServiceResponseReceived(sender: FdcClient?, serviceResponse: ServiceResponse?) {
        if (sender == null || serviceResponse == null) return

        // DFSLog.v("onServiceResponseReceived")
    }

    override fun onFdcMessageReceived(sender: FdcClient?, fdcMessage: FdcMessage?) {
        if (sender == null || fdcMessage == null) return

        // DFSLog.v("onFdcMessageReceived")
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun initialize() {
        FdcClient.getDefault().addOnFdcClientStateChangedListeners(this)
        FdcClient.getDefault().addOnFdcServiceResponseReceivedListeners(this)
        FdcClient.getDefault().addOnFdcMessageReceivedListeners(this)
        FdcClient.getDefault().start(
            SPUtil.getString(SPKeys.MIDDLE_IP),
            SPUtil.getString(SPKeys.MIDDLE_PORT).toInt(),
            GlobalData.serialNumber.get(),
            SPUtil.getString(SPKeys.MIDDLE_WORKSTATION_ID).toInt(),
            null
        )

        coroutineIO.launch {
            delay(12000)
            if (stateFusion == FdcClient.FdcClientState.Stopped) {
                onFusionEvent?.onFusionInit(FusionError.Timeout, StringUtil.get(R.string.connect_timeout))
            }
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun close() {
        val manager = this
        coroutineIO.launch {
            logout()
            FdcClient.getDefault().removeOnFdcClientStateChangedListeners(manager)
            FdcClient.getDefault().removeOnFdcServiceResponseReceivedListeners(manager)
            FdcClient.getDefault().removeOnFdcMessageReceivedListeners(manager)
            FdcClient.getDefault().stop()
            firstLink = false
        }
    }

    fun restart() {
        coroutineIO.launch {
            close()
            delay(timeoutMax.toLong() + 1000L)
            initialize()
        }
    }

    /**
     * 初始化流程中的登录+获取油品信息
     */
    fun loginFetchInfo() {
        coroutineIO.launch {
            if (!isLogin) {
                val success = if (login()) true else {
                    DFSLog.d("Fusion: retry login after logout")
                    logout()
                    delay(7000)
                    login()
                }
                if (!success) {
                    isLogin = false
                    onFusionEvent?.onFusionInit(FusionError.Login, StringUtil.get(R.string.login_fail))
                    return@launch
                } else {
                    isLogin = true
                }
            }

            val resultPumpList = requestPumpInfo()
            if (resultPumpList.success && resultPumpList.data != null) {
                if (pumpList.isNotEmpty()) pumpList.clear()
                pumpList.addAll(resultPumpList.data)
                onFusionEvent?.onFusionInit(FusionError.Success)
            } else {
                onFusionEvent?.onFusionInit(FusionError.GetNozzleInfo, StringUtil.get(R.string.fail_get_nozzle))
            }
        }
    }

    /**
     * 获取所有未结算加油订单
     * @param pumpId -1表示全部FP，其他代表某个FP
     */
    suspend fun getAllUnsettledOrder(pumpId: Int = -1) = suspendCoroutine<DFSResult<List<DeviceClass>>> {
        ThreadUtil.io {
            FdcClient.getDefault().sendGetAvailableFuelSaleTrxs(pumpId, { _, response ->
                coroutineIO.launch {
                    if (response == null || response !is ServiceResponseGetAvailableFuelSaleTrxs) {
                        it.resume(DFSResult.fail(R.string.fail_get_order))
                    } else if (response.singleFdcData == null || response.singleDeviceClass == null) {
                        it.resume(DFSResult.success(emptyList()))
                    } else {
                        DFSLog.v("uncleared orders of pump $pumpId", response.singleFdcData.deviceClasses)
                        val unsettledList = response.singleFdcData.deviceClasses.filter { !it.isClear }
                        val resultDetailList = unsettledList
                            .map { dc -> async { getOrderDetail(dc) } }
                            .map { deferred -> deferred.await() }
                            .filter { it.success }
                            .mapNotNull {
                                it.data?.productName =
                                    pumpList.find { pump -> pump.pumpId == it.data?.pumpNo }?.oilName
                                        ?: ""
                                it.data
                            }
                        it.resume(DFSResult.success(resultDetailList))
                    }
                }
            }, 4000)
        }
    }

    /**
     * 挂起函数：获取订单详情
     */
    private suspend fun getOrderDetail(deviceClass: DeviceClass) = suspendCoroutine<DFSResult<DeviceClass>> {
        getOrderDetailAsync(deviceClass) { asyncResult ->
            it.resume(asyncResult)
        }
    }

    /**
     * 获取订单详情
     */
    private fun getOrderDetailAsync(dcRaw: DeviceClass, callback: (result: DFSResult<DeviceClass>) -> Unit) {
        ThreadUtil.io {
            FdcClient.getDefault().sendGetFuelSalesTrxDetailsRequestAsync(dcRaw.pumpNo, dcRaw.transactionSeqNo, dcRaw.releaseTokenAttribute, { _, response ->
                if (response == null || response !is ServiceResponseGetFuelSalesTrxDetails || response.fdcData.isEmpty() || response.fdcData[0].deviceClasses.isEmpty()) {
                    callback(DFSResult.fail(R.string.fail_get_order_detail))
                } else {
                    val dc = response.fdcData[0].deviceClasses[0]
                    // 迷惑的数据需要 hack 一下
                    dc.releaseTokenAttribute = dcRaw.releaseTokenAttribute
                    val correctNozzle = pumpList.find {
                        it.pumpId == dc.pumpNo
                    }?.nozzleList?.find {
                        it.logicId == dc.nozzleNo
                    }
                    correctNozzle?.let {
                        dc.nozzleNo = it.physicalId
                        dc.logicNo = it.logicId
                    }
                    callback(DFSResult.success(dc))
                }
            }, 3000)
        }
    }

    /**
     * 消单
     * @desc 返回的 dc.isLock 不会发生改变 FIXME
     */
    suspend fun clearOrder(dc: DeviceClass): DFSResult<Boolean> {
        return if (dc.isClear) {
            DFSResult.success(true)
        } else if (dc.releaseTokenAttribute == null || !ValidateUtil.isPositiveInt(dc.releaseTokenAttribute)) {
            DFSResult.fail(R.string.wrong_order_info)
        } else {
            clearOrder(dc.pumpNo, dc.transactionSeqNo, dc.releaseTokenAttribute.toInt())
        }
    }
    suspend fun clearOrder(pumpId: Int, transactionNo: String, token: Int,) = suspendCoroutine<DFSResult<Boolean>> {
        ThreadUtil.io {
            FdcClient.getDefault().sendClearFuelSaleTrxRequestAsync(pumpId, transactionNo, token, { _, response ->
                if (response != null && response.singleDeviceClass != null && response.overallResult.equals("Success", true)) {
                    DFSLog.i("clear order[$pumpId-$transactionNo-$token] succeed")
                    it.resume(DFSResult.success(true))
                } else {
                    DFSLog.e("FusionManager.clearOrder failed", response)
                    it.resume(DFSResult.fail(R.string.fail_lock_order))
                }
            },4000)
        }
    }

    /**
     * 锁定/解锁订单
     */
    suspend fun lockOrder(dc: DeviceClass, lock: Boolean): DFSResult<Boolean> {
        return if (dc.isLock == lock) {
            DFSResult.success(true)
        } else if (dc.releaseTokenAttribute == null || !ValidateUtil.isPositiveInt(dc.releaseTokenAttribute)) {
            DFSResult.fail(R.string.wrong_order_info)
        } else {
            lockOrder(dc.pumpNo, dc.transactionSeqNo, dc.releaseTokenAttribute.toInt(), lock)
        }
    }
    suspend fun lockOrder(pumpId: Int, transactionNo: String, token: Int, lock: Boolean) = suspendCoroutine<DFSResult<Boolean>> {
        ThreadUtil.io {
            val callback = OnFdcServiceResponseReceivedListener { _, response ->
                if (response != null && response.singleDeviceClass != null && response.overallResult.equals("Success", true)) {
                    it.resume(DFSResult.success(true))
                } else {
                    it.resume(DFSResult.fail(response?.overallResult ?: StringUtil.get(R.string.fail_operate)))
                }
            }
            if (lock) {
                FdcClient.getDefault().sendLockFuelSaleTrxRequestAsync(pumpId, transactionNo, token, callback, 4000)
            } else {
                FdcClient.getDefault().sendUnLockFuelSaleTrxRequestAsync(pumpId, transactionNo, token, callback, 4000)
            }
        }
    }

    suspend fun requestPumpInfo() = FuelInfoApi.requestPumpInfo()

    private suspend fun login() = suspendCoroutine<Boolean> {
        val port = SPUtil.getString(SPKeys.MIDDLE_PORT).toInt()
        FdcClient.getDefault().sendLogonRequestAsync(port, port, "00.07", { _, response ->
            if (response != null && response is ServiceResponseLogOn) {
                val status = response.singleFdcData.fdcStatus
                DFSLog.v("Fusion: try login response = $status")
                if (status.equals(FusionConstants.CODE_SUCCESS, true)) {
                    DFSLog.d("Fusion: login succeeded")
                    it.resume(true)
                } else {
                    DFSLog.d("Fusion: login failed")
                    it.resume(false)
                }
            }
        }, timeoutMax)
    }

    private suspend fun logout() = suspendCoroutine<Boolean> {
        FdcClient.getDefault().sendLogOffRequestAsync({ _, response ->
            val status = response?.singleFdcData?.fdcStatus
            DFSLog.v("Fusion: try logout response = $status")
            if (status != null && status.equals(FusionConstants.CODE_SUCCESS, true)) {
                DFSLog.d("Fusion: logout succeeded")
                it.resume(true)
            } else {
                DFSLog.d("Fusion: logout failed")
                it.resume(false)
            }
        }, timeoutMax)
    }
}