package com.doverfuelingsolutions.issp.api

import android.util.Base64
import com.doverfuelingsolutions.issp.DFSApplication
import com.doverfuelingsolutions.issp.R
import com.doverfuelingsolutions.issp.api.basic.RetrofitUtil
import com.doverfuelingsolutions.issp.api.dto.*
import com.doverfuelingsolutions.issp.api.service.ServiceBase
import com.doverfuelingsolutions.issp.api.service.ServiceConfig
import com.doverfuelingsolutions.issp.api.service.ServiceLogin
import com.doverfuelingsolutions.issp.api.service.ServiceTrx
import com.doverfuelingsolutions.issp.data.DataStore
import com.doverfuelingsolutions.issp.utils.StringUtil
import com.doverfuelingsolutions.issp.utils.log.DFSLog
import com.wayne.www.waynelib.webservice.entity.ClientPosTrx
import com.wayne.www.waynelib.webservice.entity.PosTrxType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.json.JSONArray
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

object CloudApi {

    private val retrofit: Retrofit by lazyOf(RetrofitUtil.getGsonBuilder().baseUrl(WayneApiConfig.HOST_BASE).build())
    private val retrofitAuthBase: Retrofit by lazyOf(RetrofitUtil.getAuthBuilder().baseUrl(WayneApiConfig.HOST_BASE).build())
    private val retrofitAuthTrx: Retrofit by lazyOf(RetrofitUtil.getAuthBuilder().baseUrl(WayneApiConfig.HOST_TRX).build())
    private val retrofitAuthConfig: Retrofit by lazyOf(RetrofitUtil.getAuthBuilder().baseUrl(WayneApiConfig.HOST_CONFIG).build())

    private val serviceLogin: ServiceLogin by lazyOf(retrofit.create(ServiceLogin::class.java))
    private val serviceBase: ServiceBase by lazyOf(retrofitAuthBase.create(ServiceBase::class.java))
    private val serviceTrx: ServiceTrx by lazyOf(retrofitAuthTrx.create(ServiceTrx::class.java))
    private val serviceConfig: ServiceConfig by lazyOf(retrofitAuthConfig.create(ServiceConfig::class.java))

    suspend fun login(accountName: String, password: String): DFSResult<*> {
        val resultLogin = loginOnly(accountName, password)
        deviceSessionInfo()
        stationLogo()
        return if (resultLogin.success) {
            notifyTrx(PosTrxType.LogOn)
        } else {
            resultLogin
        }
    }

    private suspend fun loginOnly(accountName: String, password: String) = suspendCoroutine<DFSResult<ResultLogin>> {
        serviceLogin.login(accountName, password).enqueue(object : Callback<ResultLogin> {

            override fun onResponse(call: Call<ResultLogin>, response: Response<ResultLogin>) {
                val code = response.code()
                val body = response.body()
                if (code != 200) {
                    it.resume(DFSResult.fail(R.string.account_name_password_error))
                    return
                } else if (body != null) {
                    DataStore.accountName = accountName
                    DataStore.password = password
                    DataStore.accessToken = body.access_token
                    DataStore.accessTokenExpire = System.currentTimeMillis() + body.expires_in.toLong() * 1000
                    // [{\"Id\":\"dfc5cc2d-a5ef-4bc4-b1b0-4aa0c82a73de\",\"Name\":\"GlobalUnit\"}]
                    if (body.BusinessUnits.isNotBlank()) {
                        val jsonArray = JSONArray(body.BusinessUnits)
                        if (jsonArray.length() > 0) {
                            val obj = jsonArray.getJSONObject(0)
                            if (!obj.isNull("Id") && !obj.isNull("Name")) {
                                val buId = obj.optString("Id")
                                val buName = obj.optString("Name")
                                DataStore.buId = buId
                                DataStore.buName = buName
                                it.resume(DFSResult.success(body))
                                return
                            }
                        }
                    }
                }
                it.resume(DFSResult.fail(R.string.return_data_error))
            }

            override fun onFailure(call: Call<ResultLogin>, t: Throwable) {
                DFSLog.e(t)
                it.resume(DFSResult.fail(t.message.toString()))
            }
        })
    }

    private suspend fun notifyTrx(type: Int) = suspendCoroutine<DFSResult<Boolean>> {
        val clientPosTrx = ClientPosTrx(type)
        serviceTrx.report(clientPosTrx).enqueue(object : Callback<Any> {
            override fun onResponse(call: Call<Any>, response: Response<Any>) {
                it.resume(if (response.code() == 200) {
                    DFSResult.success(true)
                } else {
                    DFSResult.fail(StringUtil.get(R.string.login_fail, response.errorBody()?.string() ?: response.message()))
                })
            }

            override fun onFailure(call: Call<Any>, t: Throwable) {
                DFSLog.e(t)
                it.resume(DFSResult.fail(t.message.toString()))
            }
        })
    }

    private suspend fun deviceSessionInfo() = suspendCoroutine<DFSResult<ResultDeviceSessionInfo>> {
        serviceBase.deviceSessionInfo(DataStore.sn).enqueue(object : Callback<ResultDeviceSessionInfo> {
            override fun onResponse(call: Call<ResultDeviceSessionInfo>, response: Response<ResultDeviceSessionInfo>) {
                val code = response.code()
                val body = response.body()
                if (code != 200 || body == null) {
                    it.resume(DFSResult.fail(R.string.return_data_error))
                } else {
                    DataStore.stationTel = body.phoneNumber
                    it.resume(DFSResult.success(body))
                }
            }

            override fun onFailure(call: Call<ResultDeviceSessionInfo>, t: Throwable) {
                DFSLog.e(t)
                it.resume(DFSResult.fail(R.string.return_data_error))
            }
        })
    }

    private suspend fun config(type: String) = suspendCoroutine<DFSResult<ResultConfigDescItem>> {
        serviceConfig.config(DataStore.buId, type).enqueue(object : Callback<Array<ResultConfig>> {
            override fun onResponse(call: Call<Array<ResultConfig>>, response: Response<Array<ResultConfig>>) {
                val code = response.code()
                val body = response.body()
                if (code == 200 && !body.isNullOrEmpty() && body[0].configFileDescriptions.isNotEmpty()) {
                    if (body[0].configFileDescriptions.isNotEmpty()) {
                        val obj = body[0].configFileDescriptions[0]
                        it.resume(DFSResult.success(ResultConfigDescItem(obj.id, obj.name)))
                        return
                    }
                }
                it.resume(DFSResult.fail(R.string.return_data_error))
            }

            override fun onFailure(call: Call<Array<ResultConfig>>, t: Throwable) {
                DFSLog.e("WayneApi.serviceConfig.config.onFailure", t)
                it.resume(DFSResult.fail(R.string.req_fail))
            }
        })
    }

    private suspend fun file(id: String) = suspendCoroutine<DFSResult<String>> {
        serviceConfig.file(id).enqueue(object : Callback<Array<ResultFile>> {
            override fun onResponse(call: Call<Array<ResultFile>>, response: Response<Array<ResultFile>>) {
                val code = response.code()
                val body = response.body()
                if (code != 200 || body.isNullOrEmpty() || body[0].value.isBlank()) {
                    it.resume(DFSResult.fail(R.string.return_data_error))
                } else {
                    // TODO 需要优化
                    GlobalScope.launch(Dispatchers.IO) {
                        try {
                            val file = File(DFSApplication.instance.applicationContext.filesDir, body[0].name)
                            if (file.isFile) file.delete()
                            file.createNewFile()
                            file.writeBytes(Base64.decode(body[0].value, Base64.DEFAULT))
                            it.resume(DFSResult.success(body[0].name))
                        } catch (e: Exception) {
                            DFSLog.e("WayneApi.file.save", e)
                            it.resume(DFSResult.fail(R.string.fail_save_file))
                        }
                    }
                }
            }

            override fun onFailure(call: Call<Array<ResultFile>>, t: Throwable) {
                DFSLog.e("WayneApi.serviceConfig.file", t)
                it.resume(DFSResult.fail(R.string.req_fail))
            }
        })
    }

    private suspend fun stationLogo(): DFSResult<String> {
        val resultConfig = config(WayneApiConfig.CONFIG_LOGO)
        if (resultConfig.success && resultConfig.data!!.id.isNotEmpty()) {
            val resultFile = file(resultConfig.data.id)
            if (resultFile.success) {
                DataStore.stationLogoFile = resultFile.data!!
                return DFSResult.success(resultFile.data)
            }
        }
        return DFSResult.fail(R.string.req_fail)
    }

    suspend fun barcodeName(id: Int) = suspendCoroutine<DFSResult<ResultBarcode>> {
        serviceBase.barcodeInfo(id).enqueue(object : Callback<ResultBarcode> {
            override fun onResponse(call: Call<ResultBarcode>, response: Response<ResultBarcode>) {
                val code = response.code()
                val body = response.body()
                if (code == 200 && body != null) {
                    it.resume(DFSResult.success(body))
                    return
                }
                it.resume(DFSResult.fail(R.string.req_fail))
            }

            override fun onFailure(call: Call<ResultBarcode>, t: Throwable) {
                DFSLog.e(t)
                it.resume(DFSResult.fail(t.message.toString()))
            }
        })
    }
}