package com.system.cloudservice.service

import android.accessibilityservice.AccessibilityService
import android.content.ComponentName
import android.content.Context
import android.provider.Settings
import android.accessibilityservice.GestureDescription
import android.graphics.Path
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.coroutines.*

class AgentAccessibilityService : AccessibilityService() {

    companion object {
        private const val TAG = "A11yService"
        private var instance: AgentAccessibilityService? = null
        fun getInstance(): AgentAccessibilityService? = instance

        /** 服务已绑定到进程（正在运行） */
        fun isServiceBound(): Boolean = instance != null

        /**
         * 是否在系统设置中为本应用开启了无障碍，或当前已绑定实例。
         * 仅用 [isServiceBound] 会在「已开启但尚未 bind」时误显示未启用。
         */
        fun isEnabled(context: Context): Boolean {
            if (instance != null) return true
            val expected = ComponentName(context, AgentAccessibilityService::class.java)
            val flat = Settings.Secure.getString(
                context.applicationContext.contentResolver,
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
            ) ?: return false
            if (flat.isEmpty()) return false
            return flat.split(':').mapNotNull { ComponentName.unflattenFromString(it) }
                .any { it == expected }
        }

        /**
         * 企业授权设备可由 ADB 一次性授予 WRITE_SECURE_SETTINGS，之后冷启动自动补齐
         * 本应用无障碍服务。保留系统中已经启用的其他服务，不覆盖用户配置。
         */
        fun enableIfAuthorized(context: Context): Boolean {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
                context.checkSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
                android.content.pm.PackageManager.PERMISSION_GRANTED
            ) return false

            return try {
                val resolver = context.contentResolver
                val expected = ComponentName(context, AgentAccessibilityService::class.java)
                    .flattenToString()
                val current = Settings.Secure.getString(
                    resolver,
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                ).orEmpty().split(':').filter { it.isNotBlank() }.toMutableList()
                if (expected !in current) current += expected
                Settings.Secure.putString(
                    resolver,
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                    current.distinct().joinToString(":"),
                ) && Settings.Secure.putInt(
                    resolver,
                    Settings.Secure.ACCESSIBILITY_ENABLED,
                    1,
                )
            } catch (e: SecurityException) {
                Log.w(TAG, "WRITE_SECURE_SETTINGS 未授权，保留手动授权流程", e)
                false
            }
        }
    }

    private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())

    override fun onServiceConnected() {
        super.onServiceConnected()
        instance = this
        Log.d(TAG, "Accessibility service connected")
    }

    override fun onDestroy() {
        super.onDestroy()
        instance = null
        scope.cancel()
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
    override fun onInterrupt() {}

    fun click(x: Int, y: Int, callback: ((Boolean) -> Unit)? = null) {
        val path = Path().apply { moveTo(x.toFloat(), y.toFloat()) }
        val gesture = GestureDescription.Builder()
            .addStroke(GestureDescription.StrokeDescription(path, 0, 100))
            .build()
        dispatchGesture(gesture, object : GestureResultCallback() {
            override fun onCompleted(g: GestureDescription?) { callback?.invoke(true) }
            override fun onCancelled(g: GestureDescription?) { callback?.invoke(false) }
        }, null)
    }

    fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, duration: Long = 300, callback: ((Boolean) -> Unit)? = null) {
        val path = Path().apply {
            moveTo(x1.toFloat(), y1.toFloat())
            lineTo(x2.toFloat(), y2.toFloat())
        }
        val gesture = GestureDescription.Builder()
            .addStroke(GestureDescription.StrokeDescription(path, 0, duration))
            .build()
        dispatchGesture(gesture, object : GestureResultCallback() {
            override fun onCompleted(g: GestureDescription?) { callback?.invoke(true) }
            override fun onCancelled(g: GestureDescription?) { callback?.invoke(false) }
        }, null)
    }

    fun inputText(text: String, callback: ((Boolean) -> Unit)? = null) {
        scope.launch {
            val root = rootInActiveWindow ?: run { callback?.invoke(false); return@launch }
            val inputNode = findInputNode(root)
            if (inputNode != null) {
                inputNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS)
                delay(200)
                val args = Bundle().apply {
                    putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text)
                }
                val ok = inputNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args)
                inputNode.recycle()
                callback?.invoke(ok)
            } else {
                callback?.invoke(false)
            }
            root.recycle()
        }
    }

    fun back() { performGlobalAction(GLOBAL_ACTION_BACK) }
    fun home() { performGlobalAction(GLOBAL_ACTION_HOME) }
    fun recent() { performGlobalAction(GLOBAL_ACTION_RECENTS) }

    fun getUITree(): String {
        val root = rootInActiveWindow ?: return ""
        val sb = StringBuilder()
        dumpNode(root, sb, 0)
        root.recycle()
        return sb.toString()
    }

    /** 无 Root：按可见文案查找节点并点击（优先 ACTION_CLICK，失败则手势点中心） */
    fun clickFirstMatchingText(substring: String): Boolean {
        val root = rootInActiveWindow ?: return false
        val target = findNodeWithTextContains(root, substring) ?: run {
            root.recycle()
            return false
        }
        val clicked = target.performAction(AccessibilityNodeInfo.ACTION_CLICK) ||
            run {
                val r = Rect()
                target.getBoundsInScreen(r)
                click(r.centerX(), r.centerY())
                true
            }
        target.recycle()
        root.recycle()
        return clicked
    }

    private fun nodeTextBundle(node: AccessibilityNodeInfo): String {
        val hint = if (Build.VERSION.SDK_INT >= 26) node.hintText?.toString().orEmpty() else ""
        return "${node.text ?: ""}${node.contentDescription ?: ""}$hint"
    }

    private fun findNodeWithTextContains(node: AccessibilityNodeInfo, needle: String): AccessibilityNodeInfo? {
        if (nodeTextBundle(node).contains(needle)) return node
        for (i in 0 until node.childCount) {
            val c = node.getChild(i) ?: continue
            val f = findNodeWithTextContains(c, needle)
            if (f != null) {
                // f 可能就是 c；此处提前 recycle 会让调用方拿到失效节点，
                // 微信首页明明存在“搜索”仍会点击失败。
                return f
            }
            c.recycle()
        }
        return null
    }

    private fun findInputNode(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
        // 微信输入框常用自定义 View，不保证类名包含 EditText；以可编辑能力为准。
        if (node.isEditable ||
            node.className?.contains("EditText") == true ||
            node.actionList.any { it.id == AccessibilityNodeInfo.ACTION_SET_TEXT }
        ) return node
        for (i in 0 until node.childCount) {
            val child = node.getChild(i) ?: continue
            findInputNode(child)?.let { return it }
            child.recycle()
        }
        return null
    }

    private fun dumpNode(node: AccessibilityNodeInfo, sb: StringBuilder, depth: Int) {
        val indent = "  ".repeat(depth)
        sb.append("$indent${node.className}")
        node.text?.let { sb.append(" text=\"$it\"") }
        node.contentDescription?.let { sb.append(" desc=\"$it\"") }
        sb.append("\n")
        for (i in 0 until node.childCount) {
            val child = node.getChild(i) ?: continue
            dumpNode(child, sb, depth + 1)
            child.recycle()
        }
    }
}
