package com.example.modifier.service

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Base64
import android.util.DisplayMetrics
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.CompoundButton
import android.widget.FrameLayout
import androidx.annotation.MenuRes
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.liveData
import com.example.modifier.BuildConfig
import com.example.modifier.Global
import com.example.modifier.Global.backup
import com.example.modifier.Global.changeClashProfile
import com.example.modifier.Global.resetAll
import com.example.modifier.Global.restartModifier
import com.example.modifier.Global.stopClash
import com.example.modifier.R
import com.example.modifier.TraverseResult
import com.example.modifier.Utils
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.data.AppDatabase
import com.example.modifier.data.AppPreferences
import com.example.modifier.data.AppPreferencesRepository
import com.example.modifier.data.AppState
import com.example.modifier.data.AppStateRepository
import com.example.modifier.data.BackupItemDao
import com.example.modifier.databinding.FloatingWindowBinding
import com.example.modifier.enums.RcsConfigureState
import com.example.modifier.enums.RcsConnectionStatus
import com.example.modifier.extension.kill
import com.example.modifier.hasRootAccess
import com.example.modifier.http.ktorClient
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.request.RcsNumberRequest
import com.example.modifier.http.response.DeviceResponse
import com.example.modifier.http.response.RcsNumberResponse
import com.example.modifier.http.response.SysConfigResponse
import com.example.modifier.model.InstallApkAction
import com.example.modifier.model.SocketCallback
import com.example.modifier.model.TaskAction
import com.example.modifier.model.TaskConfig
import com.example.modifier.model.TaskExecutionResult
import com.example.modifier.model.TelephonyConfig
import com.example.modifier.serializer.Json
import com.example.modifier.shellRun
import com.google.android.material.color.DynamicColors
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.plugins.resources.post
import io.ktor.client.plugins.resources.put
import io.ktor.client.plugins.timeout
import io.ktor.client.request.prepareGet
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.isEmpty
import io.ktor.utils.io.core.readBytes
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.encodeToString
import org.apache.commons.collections4.queue.CircularFifoQueue
import org.apache.commons.lang3.RandomStringUtils
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import java.util.Optional
import java.util.Timer
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.resume
import kotlin.math.max
import kotlin.math.min
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

@SuppressLint("SetTextI18n")
class ModifierService : AccessibilityService(), Emitter.Listener {
    companion object {
        private const val TAG = "ModifierService1"

        const val NAME: String = BuildConfig.APPLICATION_ID + ".service.ModifierService"

        @JvmStatic
        var instance: ModifierService? = null
            private set
    }

    private val handler = Handler(Looper.getMainLooper())

    private val mSocketOpts = IO.Options()
    private lateinit var mSocket: Socket
    private lateinit var binding: FloatingWindowBinding

    private var currentTaskId = 0
    private var lastSend = 0L

    private val rcsConfigureState = MutableLiveData(RcsConfigureState.CONFIGURED)
    private val logcat = liveData(Dispatchers.IO) {
        try {
            val logs = CircularFifoQueue<String>(128)
            val p = Runtime.getRuntime().exec("su")
            p.outputStream.bufferedWriter().use { writer ->
                writer.write("logcat -c")
                writer.newLine()
                writer.flush()
                writer.write("logcat BugleRcsEngine:D *:S -v time")
                writer.newLine()
                writer.flush()
            }
            p.inputStream
                .bufferedReader()
                .useLines { lines ->
                    lines.forEach { line ->
                        if (line.contains("destState=CheckPreconditionsState")) {
                            rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
                        } else if (line.contains("destState=ReadyState")) {
                            rcsConfigureState.postValue(RcsConfigureState.READY)
                        } else if (line.contains("destState=WaitingForOtpState")) {
                            rcsConfigureState.postValue(RcsConfigureState.WAITING_FOR_OTP)
                        } else if (line.contains("destState=VerifyOtpState")) {
                            rcsConfigureState.postValue(RcsConfigureState.VERIFYING_OTP)
                        } else if (line.contains("destState=ConfiguredState")) {
                            rcsConfigureState.postValue(RcsConfigureState.CONFIGURED)
                        } else if (line.contains("destState=WaitingForRcsDefaultOnState")) {
                            rcsConfigureState.postValue(RcsConfigureState.WAITING_FOR_DEFAULT_ON)
                        } else if (line.contains("destState=WaitingForGoogleTosState")) {
                            rcsConfigureState.postValue(RcsConfigureState.WAITING_FOR_TOS)
                        } else if (line.contains("destState=RetryState")) {
                            rcsConfigureState.postValue(RcsConfigureState.RETRY)
                        } else if (line.contains("destState=ReplayRequestState")) {
                            rcsConfigureState.postValue(RcsConfigureState.REPLAY_REQUEST)
                        }
                        Regex("(?<time>\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3}) I/BugleRcsEngine\\(\\W*\\d+\\): (?<log>.*)")
                            .matchEntire(line)
                            ?.apply {
                                val time = groups["time"]?.value?.dropLast(4)
                                val log = groups["log"]?.value
                                    ?.replace(Regex("\\[\\w+-\\w+-\\w+-\\w+-\\w+]"), "")
                                    ?.replace(Regex("\\[CONTEXT.*]"), "")
                                    ?.trim()

                                if (time != null && log != null) {
                                    if (log.contains("destState=")) {
                                        logs.add("$time: $log")
                                        emit(logs.joinToString("\n"))
                                        delay(100)
                                        emit(logs.joinToString("\n"))
                                    }
                                }
                            }
                    }
                }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private val backupItemDao: BackupItemDao by lazy {
        AppDatabase.getDatabase(this).itemDao()
    }
    private val appPreferencesRepository: AppPreferencesRepository by lazy {
        AppPreferencesRepository(this)
    }
    private lateinit var appPreferences: StateFlow<AppPreferences>
    private val appStateRepository: AppStateRepository by lazy {
        AppStateRepository(this)
    }
    private lateinit var appState: StateFlow<AppState>
    private var requestMode = 1;
    private var currentActivity = ""

    fun connect() {
        try {
            if (this@ModifierService::mSocket.isInitialized) {
                mSocket.disconnect()
            }
            mSocketOpts.query =
                "model=${Build.MODEL}&name=${appPreferences.value.name}&id=${Utils.getUniqueID()}&version=${BuildConfig.VERSION_CODE}"
            mSocketOpts.transports = arrayOf("websocket")
            Log.i(TAG, "Connection query: ${mSocketOpts.query}")
            mSocket = IO.socket(appPreferences.value.server, mSocketOpts)
            mSocket.on("message", this@ModifierService)
            mSocket.on(Socket.EVENT_CONNECT) {
                Log.i(TAG, "Connected to server")
                CoroutineScope(Dispatchers.IO).launch {
                    delay(500)
                    reportDeviceStatues()
                }
            }
            mSocket.on(Socket.EVENT_DISCONNECT) {
                Log.i(TAG, "Disconnected from server")
            }
            mSocket.on(Socket.EVENT_CONNECT_ERROR) { args ->
                Log.i(TAG, "Connection error: " + args[0])
                if (args[0] is Exception) {
                    val e = args[0] as Exception
                    e.printStackTrace()
                }
            }
            mSocket.connect()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "Starting ModifierService")
        CoroutineScope(Dispatchers.IO).launch {
            appState = appStateRepository.getAppState()
            appPreferences = appPreferencesRepository.getAppPreferences()
            appStateRepository.updateRuntimeFlags(preparing = true)
            val hasRoot = run checkRoot@{
                repeat(30) {
                    if (hasRootAccess()) {
                        return@checkRoot true
                    }
                    delay(1000)
                }
                false
            }
            if (!hasRoot) {
                System.exit(0)
            }
            if (Global.rebooted()) {
                delay(2.minutes)
            } else {
                delay(5000)
            }
            Global.setupSystem()
            if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
                Global.killPhoneProcess(force = false)
            }
            appStateRepository.updateRuntimeFlags(preparing = false)

            connect()
            val timer = Timer()
            timer.schedule(object : java.util.TimerTask() {
                override fun run() {
                    reportDeviceStatues()
                }
            }, 0, 3000)
        }
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        Log.d(
            TAG,
            "eventType: ${event.eventType}, packageName: ${event.packageName}, className: ${event.className}"
        )
        if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (event.packageName != null && event.className != null) {
                val componentName = ComponentName(
                    event.packageName.toString(),
                    event.className.toString()
                )
                try {
                    packageManager.getActivityInfo(componentName, 0)
                    currentActivity = componentName.flattenToShortString()
                    Log.d(TAG, "Activity: $currentActivity")
                } catch (_: Exception) {
                }

            }
        }
    }

    override fun onInterrupt() {
    }

    override fun call(vararg args: Any) {
        if (args.isNotEmpty()) {
            Log.i(TAG, "Received message: " + args[0])
            if (args[0] is JSONObject) {
                val json = args[0] as JSONObject
                val action = json.optString("action")
                if ("send" == action) {
                    val data = json.optJSONObject("data")
                    if (data != null) {
                        val to = data.optString("to")
                        val body = data.optString("body")
                        CoroutineScope(Dispatchers.IO).launch {
                            send(
                                to, body, TaskConfig(
                                    rcsWait = 3000,
                                    rcsInterval = 1000,
                                    cleanCount = 10,
                                    requestNumberInterval = 50,
                                    checkConnection = true,
                                    useBackup = false,
                                    endToEndEncryption = true
                                )
                            )
                        }
                    }
                } else if ("task" == action) {
                    val taskAction = Json.decodeFromString<TaskAction>(json.toString())

                    CoroutineScope(Dispatchers.IO).launch {
                        runTask(taskAction)
                    }
                } else if ("installApk" == action) {
                    val installApkAction = Json.decodeFromString<InstallApkAction>(json.toString())

                    CoroutineScope(Dispatchers.IO).launch {
                        installApk(installApkAction)
                    }
                }
            }
        }
    }

    private suspend fun installApk(installApkAction: InstallApkAction) {
        try {
            val file = withContext(Dispatchers.IO) {
                File.createTempFile("files", ".apk")
            }

            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}")
            shellRun("pm install -d -r ${file.path}")
            mSocket.emit(
                "callback",
                JSONObject(
                    Json.encodeToString(
                        SocketCallback<String>(
                            id = installApkAction.id,
                            status = 0,
                        )
                    )
                )
            )
        } catch (e: Exception) {
            Log.e("Modifier", "Failed to install apk", e)
            mSocket.emit(
                "callback",
                JSONObject(
                    Json.encodeToString(
                        SocketCallback<String>(
                            id = installApkAction.id,
                            status = -1,
                            error = e.message
                        )
                    )
                )
            )
        }
    }

    private suspend fun runTask(taskAction: TaskAction) {
        if (appState.value.busy) {
            mSocket.emit(
                "callback",
                JSONObject(
                    Json.encodeToString(
                        SocketCallback<String>(
                            id = taskAction.id,
                            status = -1,
                            error = "another task is running"
                        )
                    )
                )
            )
            return
        }

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

            if (taskAction.data.config.checkConnection) {
                appStateRepository.updateRuntimeFlags(checkingConnection = true)
                if (!checkRcsAvailability()) {
                    mSocket.emit(
                        "callback",
                        JSONObject(
                            Json.encodeToString(
                                SocketCallback<String>(
                                    id = taskAction.id,
                                    status = -1,
                                    error = "RCS not available"
                                )
                            )
                        )
                    )
                    requestNumber()
                    appStateRepository.updateRuntimeFlags(checkingConnection = false)
                    return
                }
                appStateRepository.updateRuntimeFlags(checkingConnection = false)
            }

            appStateRepository.updateRuntimeFlags(running = true)
            val success = ArrayList<Int>()
            val fail = ArrayList<Int>()
            for (i in 0 until taskAction.data.tasks.size) {
                val taskItem = taskAction.data.tasks[i]
                try {
                    if (send(
                            taskItem.number,
                            taskItem.message,
                            taskConfig
                        )
                    ) {
                        success.add(taskItem.id)
                    } else {
                        fail.add(taskItem.id)
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "runTaskError: ${e.message}", e)
                    fail.add(taskItem.id)
                }
            }
            shellRun(CMD_BACK)
            mSocket.emit(
                "callback",
                JSONObject(
                    Json.encodeToString(
                        SocketCallback(
                            id = taskAction.id,
                            status = 0,
                            data = TaskExecutionResult(success, fail)
                        )
                    )
                )
            )
            if (taskConfig.requestNumberInterval in 1..appState.value.successNum) {
                delay(3000)
                requestNumber()
            } else if (taskConfig.cleanCount in 1..appState.value.executedNum) {
                delay(3000)
                Global.clearConv();
                shellRun(CMD_MESSAGING_APP)
                delay(3000)
                appStateRepository.resetExecutedNum()
            } else {
                delay(2000)
            }
            appStateRepository.updateRuntimeFlags(running = false)
        } catch (e: Exception) {
            Log.e(TAG, "runTaskError: ${e.message}", e)
            mSocket.emit(
                "callback",
                JSONObject(
                    Json.encodeToString(
                        SocketCallback<String>(
                            id = taskAction.id,
                            status = -1,
                            error = e.message
                        )
                    )
                )
            )
            appStateRepository.updateRuntimeFlags(running = false)
        }
    }

    private fun smsIntent(to: String, body: String): Intent {
        val intent = Intent(Intent.ACTION_SENDTO)
        intent.data = Uri.parse("sms:$to")
        intent.putExtra("sms_body", body)
        intent.putExtra("exit_on_sent", true)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        intent.setPackage("com.google.android.apps.messaging")
        return intent
    }

    private suspend fun send(
        to: String,
        body: String,
        taskConfig: TaskConfig
    ): Boolean {
        Log.i(TAG, "Sending SMS to $to: $body")
        startActivity(smsIntent(to, body))
        try {
            Log.i(TAG, "Command executed successfully, waiting for app to open...")
            delay(1000)
            var success = false
            var traverseResult = TraverseResult()
            withTimeoutOrNull(taskConfig.rcsWait) {
                while (true) {
                    val root = rootInActiveWindow
                    traverseResult = TraverseResult()
                    traverseNode(root, traverseResult)
                    if (traverseResult.isRcsCapable) {
                        if (traverseResult.sendBtn != null) {
                            if (taskConfig.endToEndEncryption) {
                                if (traverseResult.encrypted) {
                                    break
                                }
                            } else {
                                break
                            }
                        }
                    }
                    delay(200)
                }
            }
            if (traverseResult.isRcsCapable) {
                if (traverseResult.sendBtn == null) {
                    Log.i(TAG, "Send button not found")
                } else {
                    Log.i(TAG, "Clicking send button")

                    val dt = System.currentTimeMillis() - lastSend
                    if (taskConfig.rcsInterval > 0 && dt < taskConfig.rcsInterval) {
                        Log.i(TAG, "Waiting for RCS interval")
                        delay(taskConfig.rcsInterval - dt)
                    }
                    traverseResult.sendBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                    lastSend = System.currentTimeMillis()
                    success = true
                }
            } else {
                Log.i(TAG, "RCS not detected")
            }
            appStateRepository.incrementExecutedNum(success)
            Log.i(
                TAG,
                "executedNum: ${appState.value.executedNum}, successNum: ${appState.value.successNum}"
            )
            delay(1000)
            return success
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    fun getNodes(node: AccessibilityNodeInfo? = null): List<AccessibilityNodeInfo> {
        fun traverseChildren(node: AccessibilityNodeInfo): List<AccessibilityNodeInfo> {
            val result = mutableListOf<AccessibilityNodeInfo>()
            for (i in 0 until node.childCount) {
                val child = node.getChild(i)
                result.add(child)
                result.addAll(traverseChildren(child))
            }
            return result
        }
        return traverseChildren(node ?: rootInActiveWindow)
    }

    private fun traverseNode(node: AccessibilityNodeInfo?, result: TraverseResult) {
        if (node == null) {
            return
        }

        val packageInfo = packageManager.getPackageInfo(
            node.packageName.toString(), 0
        )

        val className = node.className.toString()
        val name = node.viewIdResourceName
        val text = Optional.ofNullable(node.text).map { obj: CharSequence -> obj.toString() }
            .orElse(null)
        val id = node.viewIdResourceName

        Log.d(TAG, "Node: class=$className, text=$text, name=$name, id=$id")

        if ("Compose:Draft:Send" == name) {
            result.sendBtn = node
        }

        if ("com.google.android.apps.messaging:id/send_message_button_icon" == id) {
            result.sendBtn = node
        }

        if (text != null && (text.contains("RCS 聊天") || text.contains("RCS chat"))) {
            result.isRcsCapable = true
        }

        if ("com.google.android.apps.messaging:id/tombstone_message" == id) {
            result.isRcsCapable = text.contains("聊天") || text.contains("Chatting with")
        }

        if (text != null && (text.contains("Turn on RCS chats") || text.contains("开启 RCS 聊天功能")
                    || text.contains("Enable chat features") || text.contains("启用聊天功能"))
        ) {
            fun findSwitch(node: AccessibilityNodeInfo): Boolean {
                if ("com.google.android.apps.messaging:id/switchWidget" == node.viewIdResourceName) {
                    result.rcsSwitch = node
                    return true
                }
                for (i in 0 until node.childCount) {
                    val child = node.getChild(i)
                    if (findSwitch(child)) {
                        return true
                    }
                }
                return false
            }
            findSwitch(node.parent.parent)
        }

        if ("com.google.android.apps.messaging:id/rcs_sim_status_status_text" == id) {
            if (text.lowercase().contains("connected") || text.lowercase().contains("已连接")) {
                result.rcsConnectionStatus = RcsConnectionStatus.CONNECTED
            }
        }

        if ("android:id/title" == id) {
            when (text) {
                "状态：已连接" -> result.rcsConnectionStatus = RcsConnectionStatus.CONNECTED
                "Status: Connected" -> result.rcsConnectionStatus = RcsConnectionStatus.CONNECTED
            }
        }

        if (text != null && (text.contains("end-to-end encrypted") || text.contains("已经过端到端加密"))) {
            result.encrypted = true
        }

        if (node.childCount != 0) {
            for (i in 0 until node.childCount) {
                traverseNode(node.getChild(i), result)
            }
        }
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onServiceConnected() {
        super.onServiceConnected()

        instance = this

        val info = AccessibilityServiceInfo()
        info.flags = AccessibilityServiceInfo.DEFAULT or
                AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS or
                AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
        info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN
        info.notificationTimeout = 100
        this.serviceInfo = info

        val displayMetrics = DisplayMetrics()
        val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
        windowManager.defaultDisplay.getMetrics(displayMetrics)
        val height = displayMetrics.heightPixels
        val width = displayMetrics.widthPixels

        val mLayout = FrameLayout(this)
        val layoutParams = WindowManager.LayoutParams()
        layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
        layoutParams.format = PixelFormat.TRANSLUCENT
        layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
        layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
        layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
        layoutParams.x = 0
        layoutParams.y = 800
        layoutParams.gravity = Gravity.START or Gravity.TOP

        val newContext = DynamicColors.wrapContextIfAvailable(applicationContext, R.style.AppTheme)
        val inflater = LayoutInflater.from(newContext)
        binding = FloatingWindowBinding.inflate(inflater, mLayout, true)
        binding.tvVersion.text = "v${BuildConfig.VERSION_CODE}"
        windowManager.addView(mLayout, layoutParams)


        var maxX = 0
        var maxY = 0
        binding.root.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY)
        binding.root.post {
            maxX = width - binding.root.measuredWidth
            maxY = height - binding.root.measuredHeight
            Log.i(TAG, "measured: $maxX, $maxY")
            layoutParams.x = maxX
            windowManager.updateViewLayout(mLayout, layoutParams)
        }

        val downX = AtomicReference(0f)
        val downY = AtomicReference(0f)
        val downParamX = AtomicReference(0)
        val downParamY = AtomicReference(0)

        val touchListener = OnTouchListener { v, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    downX.set(event.rawX)
                    downY.set(event.rawY)
                    downParamX.set(layoutParams.x)
                    downParamY.set(layoutParams.y)
                }

                MotionEvent.ACTION_MOVE -> {
                    layoutParams.x = min(
                        max((downParamX.get() + (event.rawX - downX.get())).toDouble(), 0.0),
                        maxX.toDouble()
                    )
                        .toInt()
                    layoutParams.y = min(
                        max((downParamY.get() + (event.rawY - downY.get())).toDouble(), 0.0),
                        maxY.toDouble()
                    )
                        .toInt()
                    windowManager.updateViewLayout(mLayout, layoutParams)
                }

                MotionEvent.ACTION_UP -> {
                    return@OnTouchListener event.eventTime - event.downTime >= 200
                }
            }
            false
        }

        CoroutineScope(Dispatchers.Main).launch {
            appState.collect {
                binding.swSend.isChecked = it.send
                binding.btnReq.isEnabled = !it.requesting
                binding.tvCount.text = "${it.successNum} / ${it.executedNum}"
                withContext(Dispatchers.IO) {
                    reportDeviceStatues()
                }
            }
        }
        CoroutineScope(Dispatchers.Main).launch {
            appPreferences.collect {
                binding.swSend.text = it.name
            }
        }
        binding.swSend.setOnTouchListener(touchListener)
        binding.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
            CoroutineScope(Dispatchers.IO).launch {
                appStateRepository.updateSend(isChecked)
            }
        }

        logcat.observeForever {
            binding.tvLog.text = it
            binding.scroll.fullScroll(View.FOCUS_DOWN)
        }

        binding.btnReq.setOnClickListener {
            CoroutineScope(Dispatchers.IO).launch {
                requestNumber(reset = false, noBackup = true)
            }
        }

        binding.btnReset.setOnClickListener {
            binding.btnReset.isEnabled = false
            CoroutineScope(Dispatchers.IO).launch {
                reset()
                withContext(Dispatchers.Main) {
                    binding.btnReset.isEnabled = true
                }
            }
        }
        binding.btnMore.setOnClickListener {
            showMenu(newContext, binding.btnMore, R.menu.more)
        }
    }

    private fun showMenu(context: Context, v: View, @MenuRes menuRes: Int) {
        val popup = PopupMenu(context, v)
        popup.menuInflater.inflate(menuRes, popup.menu)
        popup.setOnMenuItemClickListener { item ->
            binding.btnMore.isEnabled = false
            CoroutineScope(Dispatchers.IO).launch {
                when (item.itemId) {
                    R.id.inspect -> {
                        delay(1500)
                        traverseNode(rootInActiveWindow, TraverseResult())
                    }

                    R.id.check_availability -> {
                        checkRcsAvailability()
                    }

                    R.id.toggle_on -> {
                        toggleRcsSwitch(true)
                    }

                    R.id.toggle_off -> {
                        toggleRcsSwitch(false)
                    }

                    R.id.clear_conv -> {
                        Global.clearConv()
                    }

                    R.id.store_numbers -> {
                        storeNumbers()
                    }

                    R.id.change_profile -> {

                    }

                    R.id.restart_modifier -> {
                        restartModifier()
                    }

                    R.id.reset_counter -> {
                        CoroutineScope(Dispatchers.IO).launch {
                            appStateRepository.resetExecutedNum()
                            appStateRepository.resetSuccessNum()
                            appStateRepository.resetRequestedNum()
                        }
                    }
                }

                withContext(Dispatchers.Main) {
                    binding.btnMore.isEnabled = true
                }
            }
            true
        }
        popup.setOnDismissListener {
            // Respond to popup being dismissed.
        }
        // Show the popup menu.
        popup.show()
    }

    private fun reportDeviceStatues() {
        if (this::mSocket.isInitialized) {
            val data = JSONObject()
            try {
                data.put("action", "updateDevice")
                val dataObj = JSONObject()
                dataObj.put("canSend", appState.value.send)
                dataObj.put("busy", appState.value.busy)
                dataObj.put("currentCountry", Global.telephonyConfig.country)
                data.put("data", dataObj)
                mSocket.emit("message", data)
            } catch (e: JSONException) {
                e.printStackTrace()
            }
        }
    }

    suspend fun waitForRcsState(
        states: Array<RcsConfigureState>,
        timeout: Duration
    ): RcsConfigureState? {
        var state: RcsConfigureState? = null
        withTimeoutOrNull(timeout) {
            withContext(Dispatchers.Main) {
                suspendCancellableCoroutine { continuation ->
                    val observer = Observer<RcsConfigureState> { value ->
                        if (states.contains(value)) {
                            state = value
                            if (isActive)
                                continuation.resume(Unit)
                        }
                    }

                    rcsConfigureState.observeForever(observer)

                    continuation.invokeOnCancellation {
                        handler.post {
                            Log.i(TAG, "removeObserver")
                            rcsConfigureState.removeObserver(observer)
                        }
                    }
                }
            }
            false
        }
        return state
    }

    suspend fun toggleRcsSwitch(state: Boolean, retry: Int = 3): Boolean {
        val res = TraverseResult()

        shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 2")
        val success = run repeatBlock@{
            repeat(retry) {
                res.rcsSwitch = null
                traverseNode(rootInActiveWindow, res)
                if (res.rcsSwitch == null) {
                    shellRun(CMD_BACK, "sleep 0.5", CMD_RCS_SETTINGS_ACTIVITY, "sleep 0.5")
                } else {
                    if (res.rcsSwitch!!.isChecked == state) {
                        return@repeatBlock true
                    }
                    val rect = Rect()
                    res.rcsSwitch!!.getBoundsInScreen(rect)
                    if (state) {
                        shellRun(
                            "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
                        )
                        val btn =
                            rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
                                .firstOrNull()
                        if (btn != null) {
                            btn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                            delay(1000)
                        }
                        while (shellRun("dumpsys activity activities | grep topResumedActivity")
                                .first.contains("RcsSettingsActivity")
                        ) {
                            shellRun(CMD_BACK)
                            delay(500)
                        }
                        shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
                    } else {
                        shellRun(
                            "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
                        )
                        rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
                            .firstOrNull()?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                        delay(1000)
                        while (shellRun("dumpsys activity activities | grep topResumedActivity")
                                .first.contains("RcsSettingsActivity")
                        ) {
                            shellRun(CMD_BACK)
                            delay(500)
                        }
                        shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
                    }
                    res.rcsSwitch = null
                    traverseNode(rootInActiveWindow, res)
                    if (res.rcsSwitch?.isChecked == state) {
                        return@repeatBlock true
                    }
                }
            }
            false
        }
        while (shellRun("dumpsys activity activities | grep topResumedActivity")
                .first.contains("RcsSettingsActivity")
        ) {
            shellRun(CMD_BACK)
            delay(500)
        }
        return success
    }

    private fun isOldVersion(): Boolean {
        val info = packageManager.getPackageInfo("com.google.android.apps.messaging", 0)
        var oldVersion = false
        if (info != null) {
            if (info.versionCode < 170545910) {
                oldVersion = true
            }
        }
        return oldVersion
    }

    private suspend fun reset() {
        if (isOldVersion()) {
            withTimeout(1.hours) {
                while (true) {
                    delay(100)
                    withContext(Dispatchers.Main) {
                        binding.tvLog.text = "Waiting for RCS switch on..."
                    }
                    rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
                    Global.saveMock()
                    resetAll()
                    var switchAppear = 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 = waitForRcsState(
                            arrayOf(RcsConfigureState.WAITING_FOR_TOS),
                            5.minutes
                        )?.let {
                            it == RcsConfigureState.WAITING_FOR_TOS
                        }
                        if (switchAppear != true) {
                            Log.e(TAG, "RCS not entered default on state, retrying...")
                            continue
                        }
                    }
                    if (!toggleRcsSwitch(false)) {
                        Log.e(TAG, "RCS switch not turned off, retrying...")
                        continue
                    }
                    if (!toggleRcsSwitch(true)) {
                        Log.e(TAG, "RCS switch not turned on, retrying...")
                        continue
                    }
                    var resetSuccess = waitForRcsState(
                        arrayOf(
                            RcsConfigureState.READY
                        ), 30.seconds
                    ).let { it == RcsConfigureState.READY }
                    if (!resetSuccess) {
                        toggleRcsSwitch(false)
                        delay(1000)
                        toggleRcsSwitch(true)
                        resetSuccess = waitForRcsState(
                            arrayOf(
                                RcsConfigureState.READY
                            ), 1.minutes
                        ).let { it == RcsConfigureState.READY }
                    }
                    Log.i(TAG, "waitForRcsState: $resetSuccess")
                    appStateRepository.resetRequestedNum()
                    if (resetSuccess) {
                        delay(3000)
                        break
                    }
                }
            }
        } else {
            withTimeout(1.hours) {
                while (true) {
                    delay(100)
                    withContext(Dispatchers.Main) {
                        binding.tvLog.text = "Waiting for RCS switch on..."
                    }
                    rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
                    Global.saveMock()
                    resetAll()
                    var switchAppear = 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 = 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 = toggleRcsSwitch(true)
                    if (!switchOn) {
                        Log.e(TAG, "RCS switch not turned on, retrying...")
                        continue
                    }
                    var resetSuccess = waitForRcsState(
                        arrayOf(
                            RcsConfigureState.READY
                        ), 30.seconds
                    ).let { it == RcsConfigureState.READY }
                    if (!resetSuccess) {
                        toggleRcsSwitch(false)
                        delay(1000)
                        toggleRcsSwitch(true)
                        resetSuccess = waitForRcsState(
                            arrayOf(
                                RcsConfigureState.READY
                            ), 1.minutes
                        ).let { it == RcsConfigureState.READY }
                    }
                    Log.i(TAG, "waitForRcsState: $resetSuccess")
                    appStateRepository.resetRequestedNum()
                    if (resetSuccess) {
                        delay(3000)
                        break
                    }
                }
            }
        }
    }

    private suspend fun requestNumber(
        reset: Boolean = false,
        noBackup: Boolean = false,
        fresh: Boolean = false
    ) {
        val color = ContextCompat.getColorStateList(binding.root.context, R.color.btn_color)
        binding.btnReq.backgroundTintList = color
        if (appPreferences.value.preventRequest) {
            return
        }
        if (appState.value.requesting) {
            return
        }

        appStateRepository.updateRuntimeFlags(requesting = true)

        if (Global.telephonyConfig.available == true) {
            backup(
                backupItemDao = backupItemDao,
                type = "auto",
                sendCount = appState.value.executedNum,
                fresh = fresh
            )
        } else {
            Global.clearConv();
        }

        appStateRepository.incrementRequestedNum()
        var requestSuccess = false
        var retry = 0
        var needRest = reset
        withTimeoutOrNull(1.hours) {
            while (true) {
                delay(200)
                needRest = needRest || retry > 2 || appState.value.requestedNum > 5
                try {
                    val device =
                        ktorClient(appPreferences.value.server).get(DeviceApi.Id(id = Utils.getUniqueID()))
                            .body<DeviceResponse>()
                    if (Global.isClashInstalled(applicationContext)) {

                        val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
                        if (TextUtils.isEmpty(device.clashProfile)) {
                            prefs.edit()
                                .remove("clash_profile")
                                .apply()
                            stopClash()
                        } else {
                            val oldProfile = prefs.getString("clash_profile", "")
                            if (oldProfile != device.clashProfile) {
                                prefs.edit()
                                    .putString("clash_profile", device.clashProfile)
                                    .apply()
                                changeClashProfile(
                                    device.pinCountry!!, Base64.encodeToString(
                                        device.clashProfile!!.toByteArray(),
                                        Base64.DEFAULT
                                    )
                                )
                                delay(5000)
                            }
                        }
                    }

                    if (requestMode == 2 && !noBackup) {
                        val backup = backupItemDao.findBackupForRestore(
                            Global.telephonyConfig.number,
                            System.currentTimeMillis() - 2 * 24 * 60 * 60 * 1000
                        )
                        if (backup != null) {
                            if (Global.restore(backup)) {
                                requestSuccess = true
                                break
                            } else {
                                backup(backupItemDao, "auto", 0)
                                continue
                            }
                        }
                    }

                    if (needRest && !appPreferences.value.preventReset) {
                        reset()
                        retry = 0
                        needRest = false
                    }

                    rcsConfigureState.postValue(RcsConfigureState.NOT_CONFIGURED)
                    withContext(Dispatchers.Main) {
                        binding.tvLog.text = "Requesting number..."
                    }

                    val req = RcsNumberRequest(
                        deviceId = Utils.getUniqueID(),
                        taskId = currentTaskId
                    )
                    if (!TextUtils.isEmpty(device.pinCountry)) {
                        req.country = device.pinCountry
                    }
                    val response = ktorClient(appPreferences.value.server).put(
                        RcsNumberApi()
                    ) {
                        contentType(ContentType.Application.Json)
                        setBody(req)
                        timeout {
                            requestTimeoutMillis = 60 * 1000
                            socketTimeoutMillis = 60 * 1000
                        }
                    }
                    var rcsNumber = response.body<RcsNumberResponse>()
                    Log.i(TAG, "requestNumber response: $rcsNumber")

                    withContext(Dispatchers.Main) {
                        binding.tvLog.text = "Requesting success, waiting for logs..."
                    }

                    Global.save(
                        TelephonyConfig(
                            rcsNumber.number,
                            rcsNumber.mcc,
                            rcsNumber.mnc,
                            Global.genICCID(rcsNumber.mnc, rcsNumber.areaCode),
                            rcsNumber.mcc + rcsNumber.mnc + RandomStringUtils.randomNumeric(
                                15 - rcsNumber.mcc.length - rcsNumber.mnc.length
                            ),
                            Utils.generateIMEI(),
                            rcsNumber.country,
                            rcsNumber.areaCode,
                            false,
                            rcsNumber.carrierId,
                            rcsNumber.carrierName,
                        )
                    )

                    shellRun(CMD_MESSAGING_APP)
                    withContext(Dispatchers.Main) {
                        binding.tvLog.text = "Waiting for logs..."
                    }

                    if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
                        Log.e(TAG, "RCS number expired, retrying...")
                        continue
                    }
                    var sendOtpTimeout = ChronoUnit.SECONDS.between(
                        LocalDateTime.now(),
                        rcsNumber.expiryTime
                    ).seconds
                    if (sendOtpTimeout < 60.seconds) {
                        Log.e(TAG, "OTP timeout too short, retrying...")
                        continue
                    }
                    if (sendOtpTimeout > 2.minutes) {
                        sendOtpTimeout = 2.minutes
                    }
                    if (waitForRcsState(
                            arrayOf(RcsConfigureState.WAITING_FOR_OTP),
                            sendOtpTimeout
                        ) != RcsConfigureState.WAITING_FOR_OTP
                    ) {
                        if (!toggleRcsSwitch(true)) {
                            needRest = true
                        }
                        if (RcsConfigureState.REPLAY_REQUEST == rcsConfigureState.value) {
                            Log.e(TAG, "REPLAY_REQUEST detected, may reset after 3 retry ($retry)")
                            retry++
                        }
                        Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
                        continue
                    }

                    launch {
                        try {
                            ktorClient(appPreferences.value.server).post(
                                RcsNumberApi.Id.OtpState(
                                    RcsNumberApi.Id(
                                        RcsNumberApi(),
                                        rcsNumber.id
                                    )
                                )
                            )
                        } catch (e: Exception) {
                            Log.e(TAG, "Send OtpState Error: ${e.message}", e)
                        }
                    }

                    if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
                        Log.e(TAG, "RCS number expired, retrying...")
                        continue
                    }

                    withTimeoutOrNull(60.seconds) {
                        while (true) {
                            try {
                                rcsNumber =
                                    ktorClient(appPreferences.value.server).get(RcsNumberApi.Id(id = rcsNumber.id))
                                        .body<RcsNumberResponse>()
                                Log.i(TAG, "wait for otp response: $rcsNumber")
                                if (rcsNumber.status == RcsNumberResponse.STATUS_SUCCESS || rcsNumber.status == RcsNumberResponse.STATUS_EXPIRED) {
                                    break
                                }
                            } catch (exception: Exception) {
                                Log.e(TAG, "wait for otp Error: ${exception.stackTrace}")
                            }
                            delay(2.seconds)
                        }
                    }

                    if (rcsNumber.status != RcsNumberResponse.STATUS_SUCCESS) {
                        Log.e(TAG, "OTP not received, retrying...")
                        continue
                    }

                    val match =
                        Regex("Your Messenger verification code is G-(\\d{6})")
                            .find(rcsNumber.message!!)
                    if (match != null) {
                        val otp = match.groupValues[1]
                        Log.i(TAG, "OTP: $otp")
                        val sender = "3538"
                        val msg = "Your Messenger verification code is G-$otp"

                        val configured = run configuring@{
                            repeat(2) {
                                Global.sendSmsIntent(sender, msg)
                                val state =
                                    waitForRcsState(
                                        arrayOf(
                                            RcsConfigureState.CONFIGURED,
                                            RcsConfigureState.RETRY
                                        ), 60.seconds
                                    )
                                when (state) {
                                    RcsConfigureState.CONFIGURED -> {
                                        return@configuring true
                                    }

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

                                    else -> {
                                        Log.e(TAG, "verifyOtp fail, retrying...")
                                    }
                                }
                            }
                            false
                        }
                        if (!configured) {
                            Log.e(TAG, "RCS not configured, retrying...")
                            continue
                        } else {
                            launch {
                                try {
                                    ktorClient(appPreferences.value.server).post(
                                        RcsNumberApi.Id.Configured(
                                            RcsNumberApi.Id(
                                                RcsNumberApi(),
                                                rcsNumber.id
                                            )
                                        )
                                    )
                                } catch (e: Exception) {
                                    Log.e(
                                        TAG,
                                        "Send ConfiguredState Error: ${e.message}",
                                        e
                                    )
                                }
                            }
                            requestSuccess = true
                            break
                        }
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "requestNumberError: ${e.message}", e)
                }
            }
        }
        if (requestSuccess) {
            Global.telephonyConfig.available = true
            Global.save()
            appStateRepository.resetSuccessNum()
            appStateRepository.resetExecutedNum()
            Log.i(TAG, "requestNumber success")
            delay(5000)
            shellRun(PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP)
            delay(2000)
        } else {
            Log.e(TAG, "requestNumber failed")
            appStateRepository.updateSend(false)
            withContext(Dispatchers.Main) {
                binding.swSend.isChecked = false
                binding.btnReq.backgroundTintList =
                    ContextCompat.getColorStateList(binding.root.context, R.color.btn_color_error)
            }
        }
        appStateRepository.updateRuntimeFlags(requesting = false)
    }

    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()
            traverseNode(rootInActiveWindow, 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 checkRcsAvailability(): Boolean {
        appStateRepository.updateRuntimeFlags(checkingConnection = true)
        val availability = run checkAvailability@{
            val rcsConnected = checkRcsConnectivity()
            if (!rcsConnected) {
                return@checkAvailability false
            }

            var config: SysConfigResponse
            val checkRcsAvailabilityNumbers = mutableListOf<String>()
            withTimeoutOrNull(60.seconds) {
                try {
                    config = ktorClient(appPreferences.value.server).get(
                        SysConfigApi.Id(
                            SysConfigApi(),
                            "check_availability_numbers"
                        )
                    )
                        .body<SysConfigResponse>()
                    Log.i(TAG, "sysConfig response: $config")
                    checkRcsAvailabilityNumbers.addAll(
                        config.value.split(",").map { it.trim() })

                } catch (exception: Exception) {
                    Log.e(TAG, "sysConfig Error: ${exception.message}", exception)
                }
            }

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

            checkRcsAvailabilityNumbers.forEach {
                startActivity(smsIntent(it, ""))
                val s = withTimeoutOrNull(5.seconds) {
                    while (true) {
                        val root = rootInActiveWindow
                        val traverseResult = TraverseResult()
                        traverseNode(root, traverseResult)
                        if (traverseResult.isRcsCapable) {
                            return@withTimeoutOrNull true
                        } else {
                            Log.i(TAG, "checkRcsAvailability: RCS not detected")
                        }
                        delay(200)
                    }
                }
                if (s == true) {
                    Log.i(TAG, "checkRcsAvailability: $it success")
                    delay(1000)
                    return@checkAvailability true
                }
            }
            false
        }
        appStateRepository.updateRuntimeFlags(checkingConnection = false)
        return availability
    }

    private suspend fun storeNumbers() {
        repeat(100) {
            requestNumber(reset = true, fresh = it > 0)
            delay(5000)
        }
    }

}
