package com.example.modifier.service

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.graphics.Path
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.accessibility.AccessibilityNodeInfo
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider.getUriForFile
import com.example.modifier.BuildConfig
import com.example.modifier.R
import com.example.modifier.TraverseResult
import com.example.modifier.constants.CMD_BACK
import com.example.modifier.constants.CMD_RCS_SETTINGS_ACTIVITY
import com.example.modifier.constants.PACKAGE_MESSAGING
import com.example.modifier.utils.currentActivity
import com.example.modifier.utils.findAllScrollable
import com.example.modifier.utils.findById
import com.example.modifier.utils.findByText
import com.example.modifier.utils.findFirstByDesc
import com.example.modifier.utils.findFirstByIdAndText
import com.example.modifier.utils.getContext
import com.example.modifier.utils.shellRun
import com.example.modifier.utils.smsIntent
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull
import java.io.File

class ScreenController(val context: AccessibilityService, private val inspector: ScreenInspector) {

    companion object {
        private const val TAG = "ScreenController"
    }

    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
                inspector.traverseNode(res)
                if (res.rcsSwitch == null) {
                    shellRun(CMD_BACK, "sleep 0.5", CMD_RCS_SETTINGS_ACTIVITY, "sleep 2")
                } 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 =
                            context.rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
                                .firstOrNull()
                        if (btn != null) {
                            btn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                            delay(1000)
                        }
                        while (currentActivity()?.contains("RcsSettingsActivity") == true ||
                            currentActivity()?.contains("TurnOffRcsActivity") == true
                        ) {
                            shellRun(CMD_BACK)
                            delay(500)
                        }
                        shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
                    } else {
                        shellRun(
                            "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
                        )
                        if (currentActivity()?.contains("TurnOffRcsActivity") == true) {
                            inspector.traverseNode(res)
                            if (res.closeBtn != null) {
                                val rect1 = Rect()
                                res.closeBtn!!.getBoundsInScreen(rect1)
                                shellRun(
                                    "input tap ${rect1.centerX()} ${rect1.centerY()}", "sleep 1",
                                )
                            }
                        } else {
                            context.rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
                                .firstOrNull()?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                        }
                        delay(1000)
                        while (currentActivity()?.contains("RcsSettingsActivity") == true ||
                            currentActivity()?.contains("TurnOffRcsActivity") == true
                        ) {
                            shellRun(CMD_BACK)
                            delay(500)
                        }
                        shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
                    }
                    res.rcsSwitch = null
                    inspector.traverseNode(res)
                    if (res.rcsSwitch?.isChecked == state) {
                        return@repeatBlock true
                    }
                }
            }
            false
        }
        while (currentActivity()?.contains("RcsSettingsActivity") == true) {
            shellRun(CMD_BACK)
            delay(500)
        }
        return success
    }

    suspend fun enableAdbWifi(): Boolean {
        val i = Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        context.startActivity(i)
        delay(500)
        var node: AccessibilityNodeInfo? = withTimeoutOrNull(10000) {
            repeat(999) {
                var n = context.rootInActiveWindow.findFirstByIdAndText(
                    "android:id/title",
                    "Wireless debugging", "无线调试"
                )
                if (n != null) {
                    return@withTimeoutOrNull n
                }
                context.rootInActiveWindow.findAllScrollable().forEach {
                    it.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
                }
                delay(500)
            }
            return@withTimeoutOrNull null
        }
        if (node == null) {
            Log.e(TAG, "not found wireless debugging menu")
            return false
        }
        Log.i(TAG, "found wireless debugging menu")
        tapNode(node)
        delay(1000)

        val switchParent = context.rootInActiveWindow
            .findAccessibilityNodeInfosByViewId("com.android.settings:id/switch_bar")
            .firstOrNull()
        if (switchParent == null) {
            Log.e(TAG, "not found wireless debugging switch")
            return false
        }

        val switch = switchParent
            .findAccessibilityNodeInfosByViewId("android:id/switch_widget").firstOrNull()
        if (switch == null) {
            Log.e(TAG, "not found wireless debugging switch")
            return false
        }

        if (switch.isChecked) {
            Log.i(TAG, "wireless debugging is already enabled")
        } else {
            Log.i(TAG, "wireless debugging is disabled")
            switchParent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
            delay(1000)
        }

        node = context.rootInActiveWindow.findFirstByIdAndText(
            "android:id/title",
            "Pair device with pairing code", "使用配对码配对设备"
        )
        if (node == null) {
            Log.e(TAG, "not found pair device with pairing code")
            return false
        }
        Log.i(TAG, "found pair device with pairing code")
        node.parent.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)

        delay(1000)
        context.rootInActiveWindow.findFirstByIdAndText("android:id/title", "IP address & Port")
        return true
    }

    fun tapNode(node: AccessibilityNodeInfo) {
        val rect = Rect()
        node.getBoundsInScreen(rect)
        val path = Path()
        path.moveTo(rect.centerX().toFloat(), rect.centerY().toFloat())
        context.dispatchGesture(
            GestureDescription.Builder().addStroke(
                GestureDescription.StrokeDescription(path, 0, 100)
            ).build(), null, null
        )
    }

    suspend fun allowDebugging() {
        delay(300)
        context.rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/alwaysUse")
            .find { !it.isChecked }?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        delay(200)
        context.rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
            .firstOrNull()?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        delay(200)
    }

    suspend fun switchDefaultSmsApp(google: Boolean = false) {
        delay(300)
        val appName = ContextCompat.getString(context, R.string.app_name)
        Log.i(TAG, "set $appName as default sms app")
        val nodes =
            context.rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.permissioncontroller:id/title_and_subtitle")
        Log.i(TAG, "found ${nodes.size} title_and_subtitle")
        val node = nodes.find {
            if (google) it.findAccessibilityNodeInfosByText("信息").isNotEmpty()
                    || it.findAccessibilityNodeInfosByText("Messages").isNotEmpty()
            else it.findAccessibilityNodeInfosByText(appName).isNotEmpty()
        }
        if (node == null) {
            Log.e(TAG, "not found $appName")
            return
        }
        Log.i(TAG, "found $appName")
        node.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
    }

    suspend fun createGroup(numbers: Array<String>, timeout: Long): Boolean {
        val intent = Intent(Intent.ACTION_SENDTO)
        intent.data = Uri.parse("sms:")
        intent.putExtra("exit_on_sent", true)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        intent.setPackage(PACKAGE_MESSAGING)
        context.startActivity(intent)
        delay(1000)
        val createGroup = context.rootInActiveWindow.findByText(Regex("^Create group$"))
        if (createGroup == null) {
            Log.e(TAG, "not found Create group")
            return false
        }
        createGroup.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        delay(500)
        shellRun(*(numbers.flatMap {
            listOf("input text $it", "input keyevent 66")
        }).toTypedArray())
        val next = context.rootInActiveWindow.findByText(Regex("^Next$"))
        if (next == null) {
            Log.e(TAG, "not found Next")
            return false
        }
        next.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        delay(500)
        val done = context.rootInActiveWindow.findByText(Regex("^Done$"))
        if (done == null) {
            Log.e(TAG, "not found Done")
            return false
        }
        done.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)

        val traverseResult = TraverseResult()
        withTimeoutOrNull(timeout) {
            repeat(999) {
                inspector.traverseNode(traverseResult)
                if (traverseResult.isRcsCapable && traverseResult.sendBtn != null) {
                    return@withTimeoutOrNull
                }
                delay(200)
            }
        }
        if (!traverseResult.isRcsCapable) {
            Log.e(TAG, "RCS not capable")
        }
        return traverseResult.isRcsCapable
    }

    suspend fun groupSend(message: String?, img: File?): Boolean {
        var res = TraverseResult()
        run r1@{
            repeat(10) {
                if (it > 0) delay(200)
                inspector.traverseNode(res)
                if (res.inputField != null) {
                    return@r1
                }
            }
        }
        if (res.inputField == null) {
            Log.e(TAG, "not found input field")
            return false
        }
        res.inputField!!.performAction(AccessibilityNodeInfo.ACTION_FOCUS)
        if (message != null) {
            res.inputField!!.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, Bundle().apply {
                putCharSequence(
                    AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
                    message
                )
            })
        }
        if (img != null) {
            val uri = getUriForFile(
                context,
                "${BuildConfig.APPLICATION_ID}.fileprovider",
                img
            )
            context.grantUriPermission(
                PACKAGE_MESSAGING,
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION
            )
            val clipboardManager =
                context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
            val clipData = ClipData.newUri(context.contentResolver, "Image", uri)
            clipboardManager.setPrimaryClip(clipData)
            delay(500)
            res.inputField!!.performAction(AccessibilityNodeInfo.ACTION_PASTE)
        }
        delay(500)
        run r2@{
            repeat(10) {
                if (it > 0) delay(200)
                inspector.traverseNode(res)
                if (res.sendBtn != null) {
                    return@r2
                }
            }
        }
        if (res.sendBtn == null) {
            Log.e(TAG, "not found send button")
            return false
        }
        res.sendBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        Log.i(TAG, "groupSend: click send button")
        return true
    }

    suspend fun leaveGroup(): Boolean {
        val actionBar = run r1@{
            repeat(10) {
                if (it > 0) delay(200)
                context.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
                    "com.google.android.apps.messaging:id/action_bar_root"
                ).firstOrNull().let {
                    if (it != null) {
                        return@r1 it
                    }
                }
            }
            return@r1 null
        }
        if (actionBar == null) {
            Log.e(TAG, "leaveGroup: not found action_bar_root")
            return false
        }

        val more = actionBar.findFirstByDesc(Regex("^More$", RegexOption.IGNORE_CASE))
        if (more == null) {
            Log.e(TAG, "leaveGroup: not found More")
            return false
        }
        more.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        delay(500)
        val groupDetails =
            context.rootInActiveWindow.findByText(Regex("^Group details$", RegexOption.IGNORE_CASE))
        if (groupDetails == null) {
            Log.e(TAG, "leaveGroup: not found Group details")
            return false
        }
        groupDetails!!.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        delay(500)

        context.rootInActiveWindow.findAllScrollable().forEach {
            it.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
        }

        val leave = context.rootInActiveWindow.findById("Leave group")
        if (leave == null) {
            Log.e(TAG, "leaveGroup: not found Leave group")
            return false
        }
        leave.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        delay(500)
        val ok = context.rootInActiveWindow.findByText(Regex("^Cancel$"))
        if (ok == null) {
            Log.e(TAG, "not found OK")
            return false
        }
        Log.i(TAG, "leaveGroup: click OK")
        ok.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
        return true
    }
}