package expo.modules.rebreakprotection.accessibility

import android.accessibilityservice.AccessibilityService
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.Toast

/**
 * Tamper-Lock — sichert den Schutz gegen versehentliches/impulsives Abschalten.
 *
 * **Was dieser Service NICHT (mehr) tut:** Glücksspielseiten blocken. Das macht
 * ausschließlich der `RebreakVpnService` (DNS-Filter, ~208k Domains) — der greift
 * network-level, also in Browsern *und* Apps, und übersteht Browser-Updates. Den
 * früheren Browser-Adressleisten-Filter haben wir entfernt: redundant zum VPN,
 * fragil (View-IDs pro Browser), und führte zu Geister-Blockaden (VPN aus, a11y
 * blockt trotzdem noch).
 *
 * **Was dieser Service tut:** Wenn der User „App-Lock" aktiviert hat
 * (`tamper_armed == true`, opt-in über den App-Button) und der Schutz aktiv ist
 * (`filter_enabled == true`), verhindert er, dass schutz-relevante System-Settings
 * geöffnet werden können:
 *  - Settings → VPN → ReBreak abschalten / „Always-on" / Profil löschen
 *  - App-Info / Play-Store → ReBreak deinstallieren / Daten löschen / Force-Stop
 *  - Settings → Bedienungshilfen → den ReBreak-a11y-Service selbst abschalten
 *
 * Strategie: lauscht auf `TYPE_WINDOW_STATE_CHANGED` (Activity-Wechsel) +
 * `TYPE_WINDOW_CONTENT_CHANGED` (Dialog-Inhalt lädt bei manchen OEMs nach) aus
 * den Settings-/Installer-Packages → matcht Activity-Klasse bzw. Window-Text gegen
 * gefährliche Patterns → `GLOBAL_ACTION_BACK` + Toast. Rauskommen geht nur über
 * den regulären Deaktivierungs-Cooldown (der disarmed `tamper_armed` und stoppt
 * das VPN — danach ist dieser Service vollständig passiv).
 *
 * Bypass-Vektoren die das NICHT abdeckt: Safe-Mode-Reboot (Apps off), ADB/Root.
 */
class RebreakAccessibilityService : AccessibilityService() {

    private val mainHandler = Handler(Looper.getMainLooper())
    private var lastSettingsCheck: Long = 0L
    /** Nach einem TAMPER-BLOCK: kurz keinen weiteren Block triggern. Verhindert
     *  Toast-Spam wenn alte Keyword-Matches in Page-Transitions nachziehen. */
    private var lastBlockAt: Long = 0L
    private val POST_BLOCK_COOLDOWN_MS = 3000L

    override fun onServiceConnected() {
        super.onServiceConnected()
        Log.i(TAG, "tamper-lock service connected")
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
        if (event == null) return

        // Globaler Kill-Switch: Dieser Service tut NUR etwas, wenn der Schutz
        // aktiv ist (`filter_enabled`) UND der User „App-Lock" explizit armed hat
        // (`tamper_armed`). Beides muss stimmen — sonst vollständig passiv (auch
        // kein Logging). Deckt ab: frisches Onboarding, abgelaufener Cooldown
        // (disarmTamperLock), und den Desync-Fall „tamper noch armed aber Schutz
        // aus" (z.B. VPN extern revoked). Der a11y-Service kann sich nicht selbst
        // deaktivieren, also ist das hier die einzige Stelle wo wir ihn stilllegen.
        if (!isTamperLockArmed() || !isProtectionEnabled()) return

        val pkg = event.packageName?.toString() ?: return
        if (pkg !in WATCHED_SETTINGS_PACKAGES) return
        if (event.eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
            event.eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) return

        handleProtectedSettingsBlock(pkg, event)
    }

    override fun onInterrupt() {
        Log.w(TAG, "service interrupted")
    }

    /**
     * Tamper-Protection: User versucht in System-Settings eine Schutz-relevante
     * Seite zu öffnen (VPN deaktivieren, App löschen, A11y für Rebreak abschalten).
     *
     * Aktiviert nur wenn der App-Lock armed UND der Schutz aktiv ist (`filter_enabled`).
     * Letzteres lässt den User nach einem legitim abgelaufenen Cooldown wieder raus.
     *
     * @return true wenn die Activity geblockt wurde
     */
    private fun handleProtectedSettingsBlock(pkg: String, event: AccessibilityEvent): Boolean {
        if (!isProtectionEnabled()) return false
        if (pkg !in WATCHED_SETTINGS_PACKAGES) return false

        // Throttle: max alle 400ms eine Settings-Inspection (CONTENT_CHANGED
        // feuert sonst hunderte Mal pro Sekunde während User scrollt)
        val now = System.currentTimeMillis()
        if (now - lastSettingsCheck < 400) return false
        lastSettingsCheck = now

        // Post-Block Cooldown: nach einem Block 3s lang keinen weiteren Block
        // damit User legitim in Settings navigieren kann ohne dass alte
        // Keyword-Matches in Page-Transitions repeatedly auslösen.
        if (now - lastBlockAt < POST_BLOCK_COOLDOWN_MS) {
            Log.d(TAG, "settings-watch: cooldown active, skipping block")
            return false
        }

        val className = event.className?.toString() ?: return false

        // DEBUG: alle Settings-Activities mitloggen damit wir OEM-Variationen sehen
        Log.i(TAG, "settings-watch: $pkg / $className")

        // Phase 1 — Class-Name-Match (ältere Stock-Android-Patterns)
        val classMatchDangerous = DANGEROUS_ACTIVITY_PATTERNS.any { pattern ->
            className.contains(pattern, ignoreCase = true)
        }

        // Phase 2 — Window-Content-Match: scannen wenn kein className-Match. OEMs
        // benutzen für Dialoge oft className die weder in unseren Patterns noch als
        // "generic container" erkannt werden (z.B. Samsung's "AppDialog"). Der
        // Keyword-Cluster-Scan ist das Safety-Net: 2 Keywords aus dem gleichen
        // Cluster = Block. False-positive-Risk gedämpft durch Throttling.
        var contentReason: String? = null
        if (!classMatchDangerous) {
            contentReason = scanWindowForDangerousContent()
        }

        val isDangerous = classMatchDangerous || contentReason != null
        if (!isDangerous) return false

        Log.w(TAG, "TAMPER-BLOCK: $pkg / $className (reason=${contentReason ?: "class-match"})")
        lastBlockAt = now  // post-block cooldown startet jetzt
        // Doppel-BACK: einmal um Activity zu schließen, einmal als Backup falls
        // erste BACK nur einen Dialog dismissed.
        performGlobalAction(GLOBAL_ACTION_BACK)
        mainHandler.postDelayed({
            performGlobalAction(GLOBAL_ACTION_BACK)
        }, 200)

        // Toast in Lyra's Stimme statt eines kühlen Shield-Icons (Android 11+
        // ignoriert setView/Custom-Layouts für App-Toasts → kein echtes Avatar-
        // Bild möglich; deshalb signiert der Text mit „Lyra:"). lyra-persona darf
        // den Wortlaut gern noch feinschleifen.
        mainHandler.post {
            Toast.makeText(
                applicationContext,
                "Lyra: Das ist während deines Schutzes gesperrt 💛 Wenn du wirklich raus willst, geht das in der App.",
                Toast.LENGTH_LONG,
            ).show()
        }
        return true
    }

    /**
     * Scannt die aktuelle Window-Hierarchie nach Texten die auf eine
     * VPN/A11y/App-Uninstall-Page hindeuten. Wird genutzt wenn die Activity
     * generisch ist (z.B. Samsung's SubSettings) — dann müssen wir den
     * Inhalt selbst inspizieren.
     */
    private fun scanWindowForDangerousContent(): String? {
        val root = rootInActiveWindow ?: return null
        val texts = mutableListOf<String>()
        collectAllText(root, texts, depth = 0)
        val joined = texts.joinToString(" | ").lowercase()
        // DEBUG: was steht eigentlich auf der Page? Hilft beim Patterns-Tuning.
        Log.d(TAG, "settings-content-text: ${joined.take(500)}")

        // High-confidence Keywords: 1 Treffer reicht (sehr spezifisch zu uns)
        for (keyword in HIGH_CONFIDENCE_KEYWORDS) {
            if (joined.contains(keyword)) {
                Log.d(TAG, "settings-watch: high-confidence keyword match: '$keyword'")
                return "high-confidence:$keyword"
            }
        }

        // Standard-Cluster: min 2 Keywords nötig (false-positive-Schutz)
        for ((cluster, keywords) in DANGEROUS_TEXT_CLUSTERS) {
            val matchCount = keywords.count { joined.contains(it) }
            if (matchCount >= 2) {
                Log.d(TAG, "settings-watch: cluster $cluster matched $matchCount keywords")
                return cluster
            }
        }
        return null
    }

    private fun collectAllText(node: AccessibilityNodeInfo?, sink: MutableList<String>, depth: Int) {
        if (node == null || depth > 10) return
        node.text?.toString()?.takeIf { it.isNotBlank() }?.let { sink.add(it) }
        node.contentDescription?.toString()?.takeIf { it.isNotBlank() }?.let { sink.add(it) }
        for (i in 0 until node.childCount) {
            collectAllText(node.getChild(i), sink, depth + 1)
        }
    }

    /** Liest den `filter_enabled`-Flag aus SharedPreferences (gleicher Storage +
     *  Key wie `RebreakProtectionModule.saveEnabled` / `KEY_ENABLED`). `disable()`
     *  setzt ihn auf false — danach ist der Schutz aus und dieser Service passiv. */
    private fun isProtectionEnabled(): Boolean {
        return try {
            val prefs = applicationContext.getSharedPreferences(
                "rebreak_filter_prefs",
                android.content.Context.MODE_PRIVATE,
            )
            prefs.getBoolean("filter_enabled", false)
        } catch (_: Exception) {
            false
        }
    }

    /** "Verriegelt"-Flag — User hat über App-Button „App-Lock" bestätigt dass
     *  Settings-Tampering blockiert werden soll. Default: false damit Onboarding
     *  nicht aussperrt; nach Cooldown-Ende disarmed `disarmTamperLock` ihn wieder. */
    private fun isTamperLockArmed(): Boolean {
        return try {
            val prefs = applicationContext.getSharedPreferences(
                "rebreak_filter_prefs",
                android.content.Context.MODE_PRIVATE,
            )
            prefs.getBoolean("tamper_armed", false)
        } catch (_: Exception) {
            false
        }
    }

    companion object {
        private const val TAG = "RebreakA11y"

        // Settings-Apps die wir auf Tamper-Versuche überwachen.
        // Stock + Samsung One UI haben unterschiedliche Package-Namen,
        // wir whitelist'n alle bekannten.
        val WATCHED_SETTINGS_PACKAGES = setOf(
            "com.android.settings",
            "com.android.vpndialogs",
            "com.android.packageinstaller",
            "com.google.android.packageinstaller",
            "com.samsung.android.app.settings",
            "com.samsung.accessibility",
            // Play Store: User könnte hier auf "Deinstallieren" tippen für Rebreak
            "com.android.vending",
        )

        /**
         * High-confidence Keywords — wenn EINER davon im Window-Content auftaucht,
         * blocken wir sofort. Hochspezifisch zu uns. Enthält sowohl die aktuelle
         * a11y-Service-Summary als auch die alte (für stale Installs / OEM-Cache).
         */
        val HIGH_CONFIDENCE_KEYWORDS = listOf(
            "rebreak filter",              // VPN-Profil-Name aus Builder.setSession
            "sichert den schutz",          // aktuelle a11y-Service-Summary
            "filtert glücksspielseiten",   // alte a11y-Service-Summary (legacy installs)
            "rebreak deinstallieren",
            "rebreak entfernen",
            "rebreak löschen",
        )

        /**
         * Standard-Cluster — min 2 Keywords pro Cluster nötig damit
         * harmlose Settings-Suche keine false-positives auslöst.
         */
        val DANGEROUS_TEXT_CLUSTERS = mapOf(
            "vpn-page" to listOf(
                "vpn",
                "rebreak filter",  // unser Profil-Name (siehe Builder.setSession)
                "always-on",
                "always-on-vpn",
                "verbindung trennen",
                "trennen",
                "verbindungen ohne vpn",
                "block connections",
                "vpn-profil",
                "konto entfernen",
                "vergessen",
                "always-on vpn",
            ),
            "a11y-page" to listOf(
                "bedienungshilfe",
                "eingabehilfe",
                "accessibility",
                "sichert den schutz",   // unsere aktuelle a11y-Service-Summary
                "filtert glücksspiel",  // alte a11y-Service-Summary (legacy installs)
                "rebreak filter",
                "installierte apps",
                "installed services",
                "downloaded apps",
                "berechtigung erteilen",
                "service deaktivieren",
                "service ausschalten",
            ),
            "uninstall-page" to listOf(
                "deinstallieren",
                "uninstall",
                "rebreak",
                "möchten sie diese app",
                "do you want to uninstall",
                "app entfernen",
                "force stop",
                "stopp erzwingen",
                "speicher",
                "daten löschen",
                "clear data",
                "cache leeren",
            ),
        )

        val DANGEROUS_ACTIVITY_PATTERNS = listOf(
            // VPN-Settings + VPN-Profil-Dialoge
            "VpnSettings",
            "VpnConfig",
            "VpnDialog",
            "ManageDialog",         // com.android.vpndialogs.ManageDialog
            "ConfirmAddOns",        // com.android.vpndialogs.ConfirmAddOnsActivity

            // App-Deinstallieren-Dialoge + App-Info-Pages
            "Uninstaller",          // com.android.packageinstaller.UninstallerActivity
            "InstalledAppDetails",  // App-Info-Page (kann zu uninstall führen)
            "ApplicationDetails",   // Variations

            // Accessibility-Settings (paradox: A11y würde sich selbst aushebeln)
            "AccessibilitySettings",
            "AccessibilityDetails",
            "InstalledServiceActivity", // Samsung
            "AccessibilityShortcut",
        )
    }
}
