package expo.modules.rebreakprotection.vpn

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.VpnService
import android.os.Build
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.core.app.NotificationCompat
import expo.modules.rebreakprotection.filter.DnsFilter
import expo.modules.rebreakprotection.filter.HashList
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.DatagramSocket

/**
 * VpnService-Subclass — Pendant zum iOS NEFilterDataProvider.
 *
 * Architektur:
 *  - TUN-Interface mit kleiner virtueller Subnet (10.0.0.0/24)
 *  - System-DNS auf 10.0.0.1 → DNS-Queries kommen ins TUN
 *  - Worker-Thread liest Pakete, DnsFilter prüft + antwortet
 *  - Foreground-Service (mit Notification) damit Android den Service nicht
 *    aggressiv killt
 *
 * Service-Lifecycle:
 *  - START_FILTER → onStartCommand → startVpn → return START_STICKY
 *  - STOP_FILTER → onStartCommand → stopVpn → stopSelf
 *  - User schaltet VPN in System-Settings ab → onRevoke → cleanup
 */
class RebreakVpnService : VpnService() {

    private var tun: ParcelFileDescriptor? = null
    private var workerThread: Thread? = null
    @Volatile private var running = false
    private lateinit var hashList: HashList

    override fun onCreate() {
        super.onCreate()
        hashList = HashList(File(applicationContext.filesDir, BLOCKLIST_FILENAME))
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_STOP -> {
                stopVpn()
                stopSelf()
                return START_NOT_STICKY
            }
            ACTION_RELOAD -> {
                hashList.load()
                Log.i(TAG, "blocklist reloaded — ${hashList.count()} hashes")
                return START_STICKY
            }
            else -> {
                startForeground(NOTIF_ID, buildNotification())
                hashList.load()
                Log.i(TAG, "blocklist loaded — ${hashList.count()} hashes")
                startVpn()
                return START_STICKY
            }
        }
    }

    private fun startVpn() {
        if (running) return
        try {
            val builder = Builder()
                .addAddress(LOCAL_TUN_ADDR, 24)
                .addRoute(VIRTUAL_DNS_ADDR, 32) // nur DNS-Queries an unsere virtuelle DNS-IP routen
                .addDnsServer(VIRTUAL_DNS_ADDR)
                .setSession("Rebreak Filter")
                .setBlocking(true)

            // Eigene App vom TUN ausnehmen — sonst routen unsere eigenen
            // Backend-Calls durch unser TUN und können das Internet nicht erreichen
            try {
                builder.addDisallowedApplication(packageName)
            } catch (e: Exception) {
                Log.w(TAG, "addDisallowedApplication failed: ${e.message}")
            }

            tun = builder.establish() ?: run {
                Log.e(TAG, "TUN establish() returned null — wahrscheinlich VpnService-Permission entzogen")
                stopSelf()
                return
            }
            running = true
            isRunning = true
            // Prefs-Flag synchronisieren — auch wenn der VpnService extern gestartet
            // wurde (z.B. wenn User in System-Settings VPN toggle on). Sonst denkt
            // unser Plugin "VPN ist aus" obwohl Tunnel aktiv ist.
            setEnabledFlag(true)
            workerThread = Thread { runFilterLoop() }.also { it.start() }
            Log.i(TAG, "VPN established — TUN-Interface aktiv")
        } catch (e: Exception) {
            Log.e(TAG, "VPN start failed: ${e.message}", e)
            stopSelf()
        }
    }

    private fun stopVpn() {
        running = false
        isRunning = false
        try { tun?.close() } catch (_: Exception) {}
        tun = null
        try { workerThread?.interrupt() } catch (_: Exception) {}
        workerThread = null
        Log.i(TAG, "VPN stopped")
    }

    private fun runFilterLoop() {
        val tunFd = tun?.fileDescriptor ?: return
        val input = FileInputStream(tunFd)
        val output = FileOutputStream(tunFd)
        val outputLock = Any()
        val buf = ByteArray(32_767)

        while (running && !Thread.currentThread().isInterrupted) {
            try {
                val len = input.read(buf)
                if (len <= 0) continue

                // process() schreibt selbst ins TUN — entweder synchron (Block→
                // NXDOMAIN) oder async via Thread-Pool (Forward→Upstream).
                DnsFilter.process(
                    packet = buf,
                    length = len,
                    hashList = hashList,
                    output = output,
                    outputLock = outputLock,
                    protectSocket = { sock: DatagramSocket -> protect(sock) },
                )
            } catch (e: Exception) {
                if (running) {
                    Log.w(TAG, "filter-loop error: ${e.message}")
                }
                try { Thread.sleep(50) } catch (_: InterruptedException) { break }
            }
        }
    }

    override fun onRevoke() {
        Log.i(TAG, "onRevoke: User hat VPN in System-Settings deaktiviert")
        clearEnabledFlag()
        stopVpn()
        stopSelf()
        super.onRevoke()
    }

    override fun onDestroy() {
        // KEIN clearEnabledFlag hier — onDestroy feuert auch wenn System uns
        // wegen Low-Memory killt. Wir wollen dann beim nächsten Start wieder
        // auto-restart (START_STICKY), nicht "user-hat-disabled" signalisieren.
        // Nur onRevoke (= explizit User-Toggle in System-Settings) clear'd.
        stopVpn()
        super.onDestroy()
    }

    /** Persistierte Flags (gleicher Storage wie der Plugin) clearen — nur bei
     *  explizitem User-Revoke (System-Settings-Toggle aus). Mit `filter_enabled`
     *  geht auch `tamper_armed` weg: ein Tamper-Lock ohne aktiven Schutz ist
     *  Unsinn und würde den State desynchen → „verriegelt"-UI ohne Ausweg. */
    private fun clearEnabledFlag() {
        try {
            applicationContext.getSharedPreferences("rebreak_filter_prefs", Context.MODE_PRIVATE)
                .edit()
                .putBoolean("filter_enabled", false)
                .putBoolean("tamper_armed", false)
                .apply()
        } catch (e: Exception) {
            Log.w(TAG, "clearEnabledFlag failed: ${e.message}")
        }
    }

    /** Setzt die Plugin-Prefs-Flag synchron mit dem tatsächlichen Service-State. */
    private fun setEnabledFlag(value: Boolean) {
        try {
            val prefs = applicationContext.getSharedPreferences(
                "rebreak_filter_prefs",
                Context.MODE_PRIVATE,
            )
            prefs.edit().putBoolean("filter_enabled", value).apply()
        } catch (e: Exception) {
            Log.w(TAG, "setEnabledFlag($value) failed: ${e.message}")
        }
    }

    private fun buildNotification(): Notification {
        val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "Rebreak-Schutz",
                NotificationManager.IMPORTANCE_LOW,
            ).apply {
                description = "Aktiver Schutz vor Glücksspielseiten"
                setShowBadge(false)
            }
            nm.createNotificationChannel(channel)
        }

        val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
        val pendingIntent = launchIntent?.let {
            PendingIntent.getActivity(
                this, 0, it,
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
            )
        }

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Rebreak schützt dich")
            .setContentText("Glücksspielseiten werden blockiert")
            // Module-Context: kein App-eigenes Drawable verfügbar.
            // System-Drawable als Fallback bis wir ein eigenes Icon im Module bündeln.
            .setSmallIcon(android.R.drawable.ic_lock_lock)
            .setOngoing(true)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setContentIntent(pendingIntent)
            .build()
    }

    companion object {
        private const val TAG = "RebreakVpn"
        private const val CHANNEL_ID = "rebreak_filter_channel"
        private const val NOTIF_ID = 4711

        const val LOCAL_TUN_ADDR = "10.0.0.2"
        const val VIRTUAL_DNS_ADDR = "10.0.0.1"
        const val BLOCKLIST_FILENAME = "blocklist.bin"

        const val ACTION_STOP = "expo.modules.rebreakprotection.action.STOP"
        const val ACTION_START = "expo.modules.rebreakprotection.action.START"
        const val ACTION_RELOAD = "expo.modules.rebreakprotection.action.RELOAD"

        /** Process-lokale Live-Flag — ist der TUN gerade etabliert?
         *  Plugin nutzt das als zweiten Indikator (zusätzlich zur Prefs-Flag). */
        @Volatile var isRunning: Boolean = false
    }
}
