package im.angry.openeuicc.util

import android.util.Log
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager
import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.ProfileClass
import net.typeblog.lpac_jni.ProfileDownloadState

const val TAG = "LPAUtils"

val LocalProfileInfo.displayName: String
    get() = nickName.ifEmpty { name }


val LocalProfileInfo.isEnabled: Boolean
    get() = state == LocalProfileInfo.State.Enabled

val List<LocalProfileInfo>.operational: List<LocalProfileInfo>
    get() = filter { it.profileClass == ProfileClass.Operational || it.isEnabled }

val List<LocalProfileInfo>.enabled: LocalProfileInfo?
    get() = find { it.isEnabled }

val ProfileDownloadState.downloadProgress: Int
    get() = when (this) {
        is ProfileDownloadState.Preparing -> 0
        is ProfileDownloadState.Connecting -> 20
        is ProfileDownloadState.Authenticating -> 40
        is ProfileDownloadState.ConfirmingDownload -> 50
        is ProfileDownloadState.Downloading -> 60
        is ProfileDownloadState.Finalizing -> 80
    }

val List<EuiccChannel>.hasMultipleChips: Boolean
    get() = distinctBy { it.slotId }.size > 1

fun LocalProfileAssistant.switchProfile(
    iccid: String,
    enable: Boolean = false,
    refresh: Boolean = false
): Boolean =
    if (enable) {
        enableProfile(iccid, refresh)
    } else {
        disableProfile(iccid, refresh)
    }

/**
 * Disable the current active profile if any. If refresh is true, also cause a refresh command.
 * See EuiccManager.waitForReconnect()
 */
fun LocalProfileAssistant.disableActiveProfile(refresh: Boolean): Boolean =
    profiles.enabled?.let {
        Log.i(TAG, "Disabling active profile ${it.iccid}")
        disableProfile(it.iccid, refresh)
    } ?: true

/**
 * Disable the current active profile if any. If refresh is true, also cause a refresh command.
 * See EuiccManager.waitForReconnect()
 *
 * Return the iccid of the profile being disabled, or null if no active profile found or failed to
 * disable.
 */
fun LocalProfileAssistant.disableActiveProfileKeepIccId(refresh: Boolean): String? =
    profiles.enabled?.let {
        Log.i(TAG, "Disabling active profile ${it.iccid}")
        if (disableProfile(it.iccid, refresh)) {
            it.iccid
        } else {
            null
        }
    }

/**
 * Begin a "tracked" operation where notifications may be generated by the eSIM
 * Automatically handle any newly generated notification during the operation
 * if the function "op" returns true.
 *
 * This requires the EuiccChannelManager object and a slotId / portId instead of
 * just an LPA object, because a LPA might become invalid during an operation
 * that generates notifications. As such, we will end up having to reconnect
 * when this happens.
 *
 * Note that however, if reconnect is required and will not be instant, waiting
 * should be the concern of op() itself, and this function assumes that when
 * op() returns, the slotId and portId will correspond to a valid channel again.
 */
suspend inline fun EuiccChannelManager.beginTrackedOperation(
    slotId: Int,
    portId: Int,
    seId: EuiccChannel.SecureElementId,
    op: () -> Boolean
) {
    val latestSeq = withEuiccChannel(slotId, portId, seId) { channel ->
        channel.lpa.notifications.firstOrNull()?.seqNumber
            ?: 0
    }
    Log.d(TAG, "Latest notification is $latestSeq before operation")
    if (op()) {
        Log.d(TAG, "Operation has requested notification handling")
        try {
            // Note that the exact instance of "channel" might have changed here if reconnected;
            // this is why we need to use two distinct calls to withEuiccChannel()
            withEuiccChannel(slotId, portId, seId) { channel ->
                channel.lpa.notifications.filter { it.seqNumber > latestSeq }.forEach {
                    Log.d(TAG, "Handling notification $it")
                    channel.lpa.handleNotification(it.seqNumber)
                }
            }
        } catch (e: Exception) {
            // Ignore any error during notification handling
            e.printStackTrace()
        }
    }
    Log.d(TAG, "Operation complete")
}
