package com.example.modifier.service

import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.view.accessibility.AccessibilityNodeInfo
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider.getUriForFile
import coil3.ImageLoader
import coil3.request.crossfade
import com.example.modifier.BuildConfig
import com.example.modifier.baseTag
import com.example.modifier.TraverseResult
import com.example.modifier.constants.CMD_BACK
import com.example.modifier.constants.CMD_CONVERSATION_LIST_ACTIVITY
import com.example.modifier.constants.CMD_MESSAGING_APP
import com.example.modifier.constants.CMD_RCS_SETTINGS_ACTIVITY
import com.example.modifier.constants.PACKAGE_GMS
import com.example.modifier.constants.PACKAGE_MESSAGING
import com.example.modifier.enums.RcsConfigureState
import com.example.modifier.enums.RcsConnectionStatus
import com.example.modifier.enums.ReqState
import com.example.modifier.exception.RequestNumberException
import com.example.modifier.exception.RequestNumberException.Companion.ErrorCode
import com.example.modifier.extension.kill
import com.example.modifier.http.api.DeviceApi
import com.example.modifier.http.api.RcsNumberApi
import com.example.modifier.http.api.SysConfigApi
import com.example.modifier.http.downloadImage
import com.example.modifier.http.ktorClient
import com.example.modifier.http.response.SysConfigResponse
import com.example.modifier.model.InstallApkAction
import com.example.modifier.model.RunScriptAction
import com.example.modifier.model.TaskAction
import com.example.modifier.model.TaskConfig
import com.example.modifier.model.TaskExecutionResult
import com.example.modifier.model.UpdateDeviceAction
import com.example.modifier.repo.AppPrefsRepo
import com.example.modifier.repo.AppStateRepo
import com.example.modifier.repo.BackupRepository
import com.example.modifier.repo.GmsgStateRepo
import com.example.modifier.repo.SpoofedInfoRepo
import com.example.modifier.utils.clearConv
import com.example.modifier.utils.genAndroidId
import com.example.modifier.utils.genICCID
import com.example.modifier.utils.genIMEI
import com.example.modifier.utils.genIMSI
import com.example.modifier.utils.genMacAddress
import com.example.modifier.utils.getContext
import com.example.modifier.utils.isOldVersion
import com.example.modifier.utils.resetAll
import com.example.modifier.utils.shellRun
import com.example.modifier.utils.smsIntent
import com.example.modifier.utils.injectOTP
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.HttpResponseValidator
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.plugins.resources.get
import io.ktor.client.request.prepareGet
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.isEmpty
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import org.apache.commons.lang3.RandomStringUtils
import java.io.File
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

class TaskRunner(
    private val context: Context,
    private val screenInspector: ScreenInspector,
    val screenController: ScreenController,
    private val appStateRepo: AppStateRepo,
    private val appPrefsRepo: AppPrefsRepo,
    private val spoofedInfoRepo: SpoofedInfoRepo,
    val gmsgStateRepo: GmsgStateRepo,
    private val backupRepository: BackupRepository
) {
    companion object {
        private const val TAG = "$baseTag/TaskRunner"
    }

    private var lastSingleSend = 0L
    private var lastGroupSend = 0L
    private var currentTaskId = 0
    private var requestMode = 1
    private var storeNumberJob: Job? = null

    private val imageLoader = ImageLoader.Builder(context)
        .crossfade(true)
        .build()


    suspend fun send(
        to: String,
        body: String?,
        img: String?,
        taskConfig: TaskConfig
    ): Boolean {
        Log.i(TAG, "Sending SMS to $to: $body")
        var imgFile: File? = null
        if (img?.isNotBlank() == true) {
            imgFile = downloadImage(img)
        }
        context.startActivity(smsIntent(to, body, imgFile))
        try {
            Log.i(TAG, "Command executed successfully, waiting for app to open...")
            delay(1000)
            var success = false
            var traverseResult = TraverseResult()
            withTimeoutOrNull(taskConfig.singleTimeout) {
                repeat(999) {
                    traverseResult = TraverseResult()
                    screenInspector.traverseNode(traverseResult)
                    if (traverseResult.isRcsCapable && traverseResult.sendBtn != null) {
                        return@withTimeoutOrNull
                    }
                    delay(200)
                }
            }
            if (traverseResult.isRcsCapable && traverseResult.sendBtn != null && taskConfig.e2ee > 0) {
                withTimeoutOrNull(taskConfig.e2eeTimeout) {
                    repeat(999) {
                        traverseResult = TraverseResult()
                        screenInspector.traverseNode(traverseResult)
                        if (traverseResult.encrypted) {
                            return@withTimeoutOrNull
                        }
                        delay(200)
                    }
                }
            }
            if (traverseResult.isRcsCapable) {
                if (traverseResult.sendBtn == null) {
                    Log.i(TAG, "Send button not found")
                } else if (taskConfig.e2ee == 2 && !traverseResult.encrypted) {
                    Log.i(TAG, "E2EE not detected")
                } else {
                    Log.i(TAG, "Clicking send button")

                    val dt = System.currentTimeMillis() - lastSingleSend
                    if (taskConfig.singleDelay > 0 && dt < taskConfig.singleDelay) {
                        Log.i(TAG, "Waiting for RCS interval")
                        delay(taskConfig.singleDelay - dt)
                    }
                    traverseResult.sendBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                    lastSingleSend = System.currentTimeMillis()
                    success = true
                }
            } else {
                Log.i(TAG, "RCS not detected")
            }

            appStateRepo.incrementExecutedNum(
                success = success,
                num = if (img?.isNotBlank() == true && body?.isNotBlank() == true) 2 else 1
            )
            Log.i(
                TAG,
                "executedNum: ${appStateRepo.appState.value.singleExecuted}, successNum: ${appStateRepo.appState.value.singleSuccess}"
            )
            delay(2000)
            return success
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    suspend fun groupSend(
        to: Array<String>,
        body: String?,
        img: String?,
        taskConfig: TaskConfig
    ): Boolean {
        var imgFile: File? = null
        if (img?.isNotBlank() == true) {
            imgFile = downloadImage(img)
        }
        val success = run {
            if (!screenController.createGroup(to, taskConfig.groupTimeout)) {
                return@run false
            }
            val dt = System.currentTimeMillis() - lastGroupSend
            if (taskConfig.groupDelay > 0 && dt < taskConfig.groupDelay) {
                Log.i(TAG, "Waiting for group delay")
                delay(taskConfig.groupDelay - dt)
            }
            if (!screenController.groupSend(body, imgFile)) {
                return@run false
            }
            lastGroupSend = System.currentTimeMillis()
            delay(5000)
            screenController.leaveGroup()
            return@run true
        }
        appStateRepo.incrementGroupExecutedNum(1, success)
        return success
    }

    suspend fun runTask(
        taskAction: TaskAction,
        onSuccess: (List<TaskExecutionResult>) -> Unit,
        onError: (Exception) -> Unit
    ) {
        if (appStateRepo.appState.value.busy) {
            onError(Exception("device busy"))
            return
        }

        try {
            val taskConfig = taskAction.data.config
            currentTaskId = taskAction.data.taskId
            requestMode = if (taskAction.data.config.useBackup) 2 else 1

            if (!spoofedInfoRepo.spoofedInfo.value.available ||
                (taskAction.data.config.checkConnection && appStateRepo.appState.value.singleExecuted == 0 && appStateRepo.appState.value.groupExecuted == 0)
            ) {
                appStateRepo.updateRuntimeFlags(checkingConnection = true)
                if (!spoofedInfoRepo.spoofedInfo.value.available || !checkRcsA10y()) {
                    onError(Exception("RCS not available"))
                    if (appStateRepo.appState.value.failure >= 3) {
                        appStateRepo.updateRuntimeFlags(reqState = ReqState.NONE, suspended = true)
                    } else {
                        requestNumberOnTask()
                        appStateRepo.updateRuntimeFlags(checkingConnection = false)
                    }
                    return
                }
                appStateRepo.updateRuntimeFlags(checkingConnection = false)
            }

            appStateRepo.updateRuntimeFlags(running = true, checkingConnection = false)

            val tasks = mutableListOf(*taskAction.data.tasks)
            val results = mutableListOf<TaskExecutionResult>()

            while (tasks.isNotEmpty() && taskConfig.groupMode && tasks.size > 2 && appStateRepo.appState.value.groupExecuted < taskConfig.groupQty) {
                val items = tasks.take(taskConfig.groupSize)
                tasks.removeAll(items)
                val groupSent = groupSend(
                    to = items.map { it.number }.toTypedArray(),
                    body = items[0].message,
                    img = items[0].img,
                    taskConfig = taskConfig
                )
                results.addAll(items.map {
                    TaskExecutionResult(
                        id = it.id,
                        numberId = spoofedInfoRepo.spoofedInfo.value.numberId,
                        sent = 1,
                        delivery = if (groupSent) 1 else 0
                    )
                })
            }

            while (tasks.isNotEmpty() && appStateRepo.appState.value.singleExecuted < taskConfig.singleQty) {
                val taskItem = tasks[0]
                tasks.removeAt(0)
                val result = TaskExecutionResult(
                    id = taskItem.id,
                    numberId = spoofedInfoRepo.spoofedInfo.value.numberId
                )
                results.add(result)
                try {
                    gmsgStateRepo.addToWatchList(result)
                    result.sent = if (send(
                            taskItem.number,
                            taskItem.message,
                            taskItem.img,
                            taskConfig
                        )
                    ) 1 else 0
                } catch (e: Exception) {
                    Log.e(TAG, "runTaskError: ${e.message}", e)
                }
            }

            while (tasks.isNotEmpty()) {
                val taskItem = tasks[0]
                tasks.removeAt(0)
                results.add(
                    TaskExecutionResult(
                        id = taskItem.id,
                        sent = 2
                    )
                )
            }

            shellRun(CMD_BACK)
            delay(5000)
            onSuccess(results)
            if (taskConfig.singleQty in 1..appStateRepo.appState.value.singleSuccess) {
                delay(3000)
                requestNumberOnTask()
            } else if (taskConfig.cleanCount in 1..appStateRepo.appState.value.singleExecuted && !appPrefsRepo.appPrefs.value.preventClean) {
                delay(3000)
                clearConv();
                shellRun(CMD_MESSAGING_APP)
                delay(3000)
                appStateRepo.resetExecutedNum()
            } else {
                delay(2000)
            }
            appStateRepo.updateRuntimeFlags(running = false)
            if (taskAction.data.config.checkConnection && results.none { it.sent == 1 }) {
                appStateRepo.updateRuntimeFlags(checkingConnection = true)
                if (!checkRcsA10y()) {
                    onError(Exception("RCS not available"))
                    requestNumberOnTask()
                }
                appStateRepo.updateRuntimeFlags(checkingConnection = false)
            }
        } catch (e: Exception) {
            Log.e(TAG, "runTaskError: ${e.message}", e)
            onError(e)
            appStateRepo.updateRuntimeFlags(running = false)
        }
    }

    suspend fun reset(mock: Boolean = true) {
        val context = getContext()
        val dir = ContextCompat.getExternalCacheDirs(context)[0]
//        val pifPath = File(dir, "pif1.json").path
//        Utils.copyAsset(context.assets, "pif1.json", pifPath)
//        shellRun("cp $pifPath /data/adb/pif.json")
        if (isOldVersion(context)) {
            withTimeout(1.hours) {
                while (true) {
                    delay(100)
                    appStateRepo.updateRuntimeFlags(reqState = ReqState.RESET)
                    gmsgStateRepo.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
                    if (mock) {
                        spoofedInfoRepo.mock()
                    }
                    resetAll()
                    var switchAppear = gmsgStateRepo.waitForRcsState(
                        arrayOf(RcsConfigureState.WAITING_FOR_TOS),
                        2.minutes
                    )?.let {
                        it == RcsConfigureState.WAITING_FOR_TOS
                    }
                    if (switchAppear != true) {
                        shellRun(
                            PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1",
                            CMD_MESSAGING_APP
                        )
                        switchAppear = gmsgStateRepo.waitForRcsState(
                            arrayOf(RcsConfigureState.WAITING_FOR_TOS),
                            3.minutes
                        )?.let {
                            it == RcsConfigureState.WAITING_FOR_TOS
                        }
                        if (switchAppear != true) {
                            shellRun(
                                PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1",
                                CMD_MESSAGING_APP
                            )
                            switchAppear = gmsgStateRepo.waitForRcsState(
                                arrayOf(RcsConfigureState.WAITING_FOR_TOS),
                                2.minutes
                            )?.let {
                                it == RcsConfigureState.WAITING_FOR_TOS
                            }
                            if (switchAppear != true) {
                                Log.e(TAG, "RCS not entered default on state, retrying...")
                                continue
                            }
                        }
                    }
                    if (!screenController.toggleRcsSwitch(false)) {
                        Log.e(TAG, "RCS switch not turned off, retrying...")
                        continue
                    }
                    if (!screenController.toggleRcsSwitch(true)) {
                        Log.e(TAG, "RCS switch not turned on, retrying...")
                        continue
                    }
                    var resetSuccess = gmsgStateRepo.waitForRcsState(
                        arrayOf(
                            RcsConfigureState.READY
                        ), 30.seconds
                    ).let { it == RcsConfigureState.READY }
                    if (!resetSuccess) {
                        screenController.toggleRcsSwitch(false)
                        delay(1000)
                        screenController.toggleRcsSwitch(true)
                        resetSuccess = gmsgStateRepo.waitForRcsState(
                            arrayOf(
                                RcsConfigureState.READY
                            ), 1.minutes
                        ).let { it == RcsConfigureState.READY }
                    }
                    Log.i(TAG, "waitForRcsState: $resetSuccess")
                    appStateRepo.resetRequestedNum()
                    if (resetSuccess) {
                        delay(3000)
                        break
                    }
                }
            }
        } else {
            withTimeout(1.hours) {
                while (true) {
                    delay(100)
                    appStateRepo.updateRuntimeFlags(reqState = ReqState.RESET)
                    gmsgStateRepo.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
                    if (mock) {
                        spoofedInfoRepo.mock()
                    }
                    resetAll()
                    var switchAppear = gmsgStateRepo.waitForRcsState(
                        arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
                        1.minutes
                    )?.let {
                        it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
                    }
                    if (switchAppear != true) {
                        shellRun(
                            PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1",
                            CMD_MESSAGING_APP
                        )
                        switchAppear = gmsgStateRepo.waitForRcsState(
                            arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
                            2.minutes
                        )?.let {
                            it == RcsConfigureState.WAITING_FOR_DEFAULT_ON
                        }
                        if (switchAppear != true) {
                            Log.e(TAG, "RCS not entered default on state, retrying...")
                            continue
                        }
                    }
                    val switchOn = screenController.toggleRcsSwitch(true)
                    if (!switchOn) {
                        Log.e(TAG, "RCS switch not turned on, retrying...")
                        continue
                    }
                    var resetSuccess = gmsgStateRepo.waitForRcsState(
                        arrayOf(
                            RcsConfigureState.READY
                        ), 30.seconds
                    ).let { it == RcsConfigureState.READY }
                    if (!resetSuccess) {
                        screenController.toggleRcsSwitch(false)
                        delay(1000)
                        screenController.toggleRcsSwitch(true)
                        resetSuccess = gmsgStateRepo.waitForRcsState(
                            arrayOf(
                                RcsConfigureState.READY
                            ), 1.minutes
                        ).let { it == RcsConfigureState.READY }
                    }
                    Log.i(TAG, "waitForRcsState: $resetSuccess")
                    appStateRepo.resetRequestedNum()
                    if (resetSuccess) {
                        delay(3000)
                        break
                    }
                }
            }
        }
        appStateRepo.updateRuntimeFlags(reqState = ReqState.NONE)
//        shellRun("rm /data/adb/pif.json")
    }

    private suspend fun requestNumberAtomic(store: Boolean? = false) {
        appStateRepo.updateRuntimeFlags(reqState = ReqState.REQUEST)
        gmsgStateRepo.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
        val device = DeviceApi.getDevice(appPrefsRepo.appPrefs.value.id)
        val rcsNumber = RcsNumberApi.getRcsNumber(
            deviceId = appPrefsRepo.appPrefs.value.id,
            taskId = currentTaskId,
            pinCountry = device.pinCountry,
            store = store
        )
        Log.i(TAG, "requestNumber response: $rcsNumber")

        appStateRepo.updateRuntimeFlags(reqState = ReqState.OTP_1)

        spoofedInfoRepo.updateSpoofedSimInfo(
            spoofedInfoRepo.spoofedInfo.value.copy(
                numberId = rcsNumber.id,
                number = rcsNumber.number,
                mcc = rcsNumber.mcc,
                mnc = rcsNumber.mnc,
                iccid = genICCID(rcsNumber.mnc, rcsNumber.areaCode),
                imsi = genIMSI(rcsNumber.mcc + rcsNumber.mnc),
                imei = genIMEI(),
                country = rcsNumber.country,
                areaCode = rcsNumber.areaCode,
                available = false,
                carrierId = rcsNumber.carrierId,
                carrierName = rcsNumber.carrierName,
                serialNo = spoofedInfoRepo.spoofedInfo.value.serialNo,
                wifiMac = genMacAddress(),
                bssid = genMacAddress(),
                btMac = genMacAddress(),
                ethMac = genMacAddress(),
                androidId = genAndroidId(),
                gmsAid = genAndroidId(),
                rootAid = genAndroidId(),
            )
        )
        val tosAgreeJob = CoroutineScope(coroutineContext).launch {
            run tos@{
                repeat(60 * 1000 / 200) {
                    delay(500)
                    val traverseResult = TraverseResult()
                    screenInspector.traverseNode(traverseResult)
                    if (traverseResult.tosAgreeBtn != null) {
                        traverseResult.tosAgreeBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                        return@tos
                    }
                }
            }
        }
        shellRun(CMD_MESSAGING_APP)
        if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
            throw RequestNumberException(ErrorCode.CODE_NUMBER_EXPIRED)
        }
        var sendOtpTimeout =
            ChronoUnit.SECONDS.between(LocalDateTime.now(), rcsNumber.expiryTime).seconds
        if (sendOtpTimeout < 5.seconds) {
            throw RequestNumberException(ErrorCode.CODE_TIMEOUT_TOO_SHORT)
        }
        if (sendOtpTimeout > 2.minutes) {
            sendOtpTimeout = 2.minutes
        }
        if (gmsgStateRepo.waitForRcsState(
                arrayOf(RcsConfigureState.WAITING_FOR_OTP),
                sendOtpTimeout
            ) != RcsConfigureState.WAITING_FOR_OTP
        ) {
            tosAgreeJob.cancel()
            if (!screenController.toggleRcsSwitch(true)) {
                throw RequestNumberException(ErrorCode.CODE_RCS_TOGGLED_OFF)
            }
            if (RcsConfigureState.REPLAY_REQUEST == gmsgStateRepo.rcsConfigureState.value) {
                throw RequestNumberException(ErrorCode.CODE_REPLAY_RETRY)
            }
            throw RequestNumberException(ErrorCode.CODE_OTP_NOT_SENT)
        }
        tosAgreeJob.cancel()
        RcsNumberApi.notifyOtpState(rcsNumber.id)

        if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
            throw RequestNumberException(ErrorCode.CODE_NUMBER_EXPIRED)
        }

        appStateRepo.updateRuntimeFlags(reqState = ReqState.OTP_2)
        val otp = RcsNumberApi.waitForOtp(rcsNumber.id)
            ?: throw RequestNumberException(ErrorCode.CODE_OTP_NOT_RECEIVED)

        appStateRepo.updateRuntimeFlags(reqState = ReqState.CONFIG)

        val configured = run configuring@{
            repeat(2) {
                injectOTP(otp)
                val state =
                    gmsgStateRepo.waitForRcsState(
                        arrayOf(
                            RcsConfigureState.CONFIGURED,
                            RcsConfigureState.RETRY
                        ), 60.seconds
                    )
                when (state) {
                    RcsConfigureState.CONFIGURED -> {
                        return@configuring true
                    }

                    RcsConfigureState.RETRY -> {
                        gmsgStateRepo.waitForRcsState(
                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
                            60.seconds
                        )
                    }

                    else -> {
                    }
                }
            }
            false
        }
        if (!configured) {
            throw RequestNumberException(ErrorCode.CODE_OTP_VERIFY_FAILED)
        } else {
            RcsNumberApi.notifyConfigured(rcsNumber.id)
            delay(4000)
            shellRun(
                PACKAGE_GMS.kill(),
                "sleep 2",
                PACKAGE_MESSAGING.kill(),
                "sleep 1",
                CMD_MESSAGING_APP
            )
            delay(2000)
        }
    }

    suspend fun requestNumberOnTask(reset: Boolean = false, noBackup: Boolean = false) {
        appStateRepo.updateRuntimeFlags(suspended = false)
        if (appPrefsRepo.appPrefs.value.preventRequest) {
            return
        }
        if (appStateRepo.appState.value.reqState != ReqState.NONE) {
            return
        }
        appStateRepo.updateRuntimeFlags(reqState = ReqState.CLEAN)
        DeviceApi.removePinTask(appPrefsRepo.appPrefs.value.id)
        clearConv(restoreRole = false)
        if (spoofedInfoRepo.spoofedInfo.value.available) {
            backupRepository.backup(
                type = "auto",
                sendCount = appStateRepo.appState.value.singleSuccess
            )
        }

        var needRest = reset
        val requestSuccess = withTimeoutOrNull(2.hours) {
            while (true) {
                delay(200)
                try {
                    if (requestMode == 2 && !noBackup) {
                        val backup = backupRepository.findBackupForRestore(
                            spoofedInfoRepo.spoofedInfo.value.number
                        )
                        if (backup != null) {
                            AppStateRepo.instance.updateRuntimeFlags(reqState = ReqState.RESTORE)
                            if (backupRepository.restore(backup)) {
                                return@withTimeoutOrNull true
                            } else {
                                continue
                            }
                        }
                    }

                    needRest = needRest || appStateRepo.appState.value.requestedNum >= 3
                            || spoofedInfoRepo.spoofedInfo.value.available
                    needRest = needRest && !appPrefsRepo.appPrefs.value.preventReset
                    if (needRest) {
                        reset()
                        needRest = false
                    }
                    appStateRepo.incrementRequestedNum()
                    requestNumberAtomic()
                    return@withTimeoutOrNull true
                } catch (e: Exception) {
                    if (e is RequestNumberException) {
                        Log.e(TAG, "requestNumberError: ${e.message}")
                    } else {
                        Log.e(TAG, "requestNumberError: ${e.message}", e)
                    }
                }
            }
            false
        }
        if (requestSuccess == true) {
            spoofedInfoRepo.updateAvailable(available = true)
            appStateRepo.resetSuccessNum()
            appStateRepo.resetExecutedNum()
            appStateRepo.resetGroupExecutedNum()
            appStateRepo.resetGroupSuccessNum()
            Log.i(TAG, "requestNumber success")
        } else {
            Log.e(TAG, "requestNumber failed")
            appStateRepo.updateSend(false)
        }
        appStateRepo.updateRuntimeFlags(
            reqState = ReqState.NONE,
            suspended = requestSuccess != true
        )
        appStateRepo.incrementRequestedNum()
    }

    private suspend fun checkRcsConnectivity(): Boolean = run checkRcsConnection@{
        repeat(3) {
            Log.i(TAG, "Checking RCS status...")
            shellRun(
                CMD_CONVERSATION_LIST_ACTIVITY,
                CMD_RCS_SETTINGS_ACTIVITY,
                "sleep 1",
            )
            val res = TraverseResult()
            screenInspector.traverseNode(res)
            if (res.rcsConnectionStatus == RcsConnectionStatus.CONNECTED) {
                Log.i(TAG, "RCS is connected")
                shellRun(CMD_BACK)
                return@checkRcsConnection true
            } else {
                Log.i(TAG, "RCS not connected, retrying...")
            }
            shellRun(CMD_BACK, "sleep ${it * 2}")
        }
        false
    }

    suspend fun checkRcsA10y(timeout: Int = 7500, repeatNum: Int = 2): Boolean {
        val availability = run checkA10y@{
            repeat(repeatNum) {
                val rcsConnected = checkRcsConnectivity()
                if (rcsConnected) {
                    val checkRcsA10yNumbers = mutableListOf<String>()
                    try {
                        val config = ktorClient
                            .get(SysConfigApi.Id(SysConfigApi(), "check_availability_numbers"))
                            .body<SysConfigResponse>()
                        Log.i(TAG, "sysConfig response: $config")
                        checkRcsA10yNumbers.addAll(config.value.split(",").map { it.trim() })
                    } catch (exception: Exception) {
                        Log.e(TAG, "sysConfig Error: ${exception.message}", exception)
                    }

                    if (checkRcsA10yNumbers.isEmpty()) {
                        Log.e(TAG, "checkRcsA10yNumbers is empty")
                        return@checkA10y true
                    }

                    checkRcsA10yNumbers.forEach {
                        context.startActivity(smsIntent(it, "", null))
                        repeat(timeout / 200) {
                            delay(200)
                            val traverseResult = TraverseResult()
                            screenInspector.traverseNode(traverseResult)
                            if (traverseResult.isRcsCapable) {
                                return@checkA10y true
                            } else {
                                Log.i(TAG, "checkRcsA10y: RCS not detected")
                            }
                        }
                    }
                }
                shellRun(PACKAGE_MESSAGING.kill(), CMD_MESSAGING_APP, "sleep 5")
            }
            false
        }
        if (!availability) {
            RcsNumberApi.notifyWasted(spoofedInfoRepo.spoofedInfo.value.numberId)
            appStateRepo.incrementFailure()
        } else {
            appStateRepo.resetFailure()
        }
        return availability
    }

    suspend fun installApk(
        installApkAction: InstallApkAction,
        onSuccess: () -> Unit,
        onError: (Exception) -> Unit
    ) {
        try {
            val file = withContext(Dispatchers.IO) {
                File.createTempFile(
                    "temp",
                    ".apk",
                    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                )
            }

            HttpClient(OkHttp) {
                HttpResponseValidator {
                    validateResponse { response ->
                        if (response.status.value !in 200..299) {
                            throw ServerResponseException(
                                response,
                                "Error " + response.status.value.toString()
                            )
                        }
                    }
                }
            }.prepareGet(installApkAction.data.apkUrl)
                .execute { httpResponse ->
                    val channel: ByteReadChannel = httpResponse.body()
                    while (!channel.isClosedForRead) {
                        val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
                        while (!packet.isEmpty) {
                            val bytes = packet.readBytes()
                            file.appendBytes(bytes)
                        }
                    }
                }
            Log.i(TAG, "Apk file saved to ${file.path}")

            val installPath = "/data/local/tmp/${RandomStringUtils.randomAlphabetic(8)}.apk"
            shellRun(
                "cp ${file.path} $installPath",
                "pm install -d -r $installPath",
                "rm -f $installPath"
            )
            onSuccess()
            file.delete()
        } catch (e: Exception) {
            Log.e(TAG, "Failed to install apk", e)
            onError(e)
        }
    }

    suspend fun runScript(
        installApkAction: RunScriptAction,
        onSuccess: (String, String) -> Unit,
        onError: (Exception) -> Unit
    ) {
        try {
            val (out, err) = shellRun(*installApkAction.data.script.split("\n").toTypedArray())
            onSuccess(out, err)
        } catch (e: Exception) {
            Log.e(TAG, "Failed to run script", e)
            onError(e)
        }
    }

    suspend fun updateDevice(
        updateDeviceAction: UpdateDeviceAction,
        onSuccess: () -> Unit,
        onError: (Exception) -> Unit
    ) {
        try {
            if (updateDeviceAction.data.id != null) {
                appPrefsRepo.updateId(updateDeviceAction.data.id)
            }
            if (updateDeviceAction.data.name != null) {
                appPrefsRepo.updateName(updateDeviceAction.data.name)
            }
            onSuccess()
        } catch (e: Exception) {
            Log.e(TAG, "Failed to update device", e)
            onError(e)
        }
    }

    suspend fun storeNumbers(n: Int? = null) {
        if (storeNumberJob != null) {
            Log.e(TAG, "storeNumbers: another store number job is running")
            return
        }
        val num = n ?: appPrefsRepo.appPrefs.value.storeNum
        if (num <= 0) {
            Log.e(TAG, "storeNumbers: storeNum is 0")
            return
        }
        storeNumberJob = CoroutineScope(coroutineContext).launch {
            try {
                appStateRepo.updateRuntimeFlags(storing = true)
                if (spoofedInfoRepo.spoofedInfo.value.available && appStateRepo.appState.value.singleSuccess <= 5) {
                    screenController.toggleRcsSwitch(false)
                    backupRepository.backup(
                        type = "auto",
                        sendCount = appStateRepo.appState.value.singleSuccess,
                        stock = 1
                    )
                    screenController.toggleRcsSwitch(true)
                }
                var success = true
                repeat(num) {
                    if (!isActive) {
                        Log.e(TAG, "storeNumbers: job is cancelled, stopping repeat")
                        return@launch
                    }
                    Log.i(TAG, "storeNumbers: $it")
                    try {
                        if ((success || appStateRepo.appState.value.requestedNum > 3)
                            && !(it == 0 && appStateRepo.appState.value.requestedNum == 0)
                        ) {
                            reset()
                        }
                        requestNumberAtomic(store = true)
                        spoofedInfoRepo.updateAvailable(available = true)
                        screenController.toggleRcsSwitch(false)
                        backupRepository.backup(type = "auto", sendCount = 0, stock = 1)
                        RcsNumberApi.updateStockFlag(
                            id = spoofedInfoRepo.spoofedInfo.value.numberId,
                            flag = 1
                        )
                        success = true
                    } catch (e: CancellationException) {
                        Log.e(TAG, "storeNumbers: job is cancelled", e)
                    } catch (e: Exception) {
                        success = false
                        Log.e(TAG, "Error store number", e)
                    }
                    appStateRepo.incrementRequestedNum()
                }
                if (success) {
                    reset()
                }
                appStateRepo.updateRuntimeFlags(
                    reqState = ReqState.NONE,
                    storing = false
                )
                storeNumberJob = null
            } catch (e: CancellationException) {
                Log.e(TAG, "storeNumbers: job is cancelled", e)
            }
        }
    }

    fun cancelStoreNumber() {
        if (storeNumberJob != null) {
            if (storeNumberJob!!.isActive) {
                storeNumberJob!!.cancel()
            }
            appStateRepo.updateRuntimeFlags(
                reqState = ReqState.NONE,
                storing = false
            )
            storeNumberJob = null
        }
    }

    suspend fun cleanBackup(start: Long, end: Long) {
        backupRepository.cleanBackup(start, end)
    }
}