package app.netrunner.vpn

import android.app.*
import android.content.*
import android.net.VpnService
import android.os.*
import android.util.Log
import android.webkit.WebView
import androidx.activity.result.ActivityResult
import app.tauri.annotation.ActivityCallback
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.JSArray
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import uniffi.netrunner_client.Session
import uniffi.netrunner_client.SessionManager
import java.io.File
import java.net.URL

import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive

private const val TAG = "VPN_DEBUG"

@Parcelize
data class TunnelConfig @JsonCreator constructor(
    @JsonProperty("address") val address: String,
    @JsonProperty("prefix") val prefix: Int,
    @JsonProperty("dns") val dns: String,
    @JsonProperty("mtu") val mtu: Int,
    @JsonProperty("killswitchEnabled") val killswitchEnabled: Boolean,
    @JsonProperty("excludedApps") val excludedApps: List<String>,
    @JsonProperty("excludedDomains") val excludedDomains: List<String>,
    // Новое поле для режима песочницы
    @JsonProperty("allowedApps") val allowedApps: List<String>? = null,
    // Bearer-токен клиента для проверки авторизации на прокси (--require-auth);
    // null — сервер не требует авторизацию, либо приложение ещё не залогинено.
    @JsonProperty("authToken") val authToken: String? = null
) : Parcelable

class TunVpnService : VpnService() {
    private var activeSession: Session? = null
    private var vpnInterface: ParcelFileDescriptor? = null
    private var statsJob: Job? = null

    companion object {
        const val ACTION_STOP_VPN = "app.netrunner.vpn.STOP_FROM_NOTIFICATION"
        @Volatile var isServiceRunning = false
    }

    override fun onCreate() {
        super.onCreate()
        isServiceRunning = true
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForegroundNotification()
        when (intent?.action) {
            "START_VPN" -> {
                val ip = intent.getStringExtra("ip") ?: ""
                val port = intent.getIntExtra("port", 0)
                val sni = intent.getStringExtra("sni") ?: ""
                val config = if (Build.VERSION.SDK_INT >= 33) intent.getParcelableExtra("config", TunnelConfig::class.java)
                             else @Suppress("DEPRECATION") intent.getParcelableExtra("config")
                config?.let { startVpn(it, ip, port, sni) }
            }
            "STOP_VPN", ACTION_STOP_VPN -> stopServiceProperly()
        }
        return START_STICKY
    }

    private fun startVpn(config: TunnelConfig, ip: String, port: Int, sni: String) {
        val builder = Builder().addAddress(config.address, config.prefix)
            .addDnsServer(config.dns).addRoute("0.0.0.0", 0).setMtu(config.mtu)

        val allowed = config.allowedApps ?: emptyList()
        
        // ЛОГИКА ФИЛЬТРАЦИИ
        if (allowed.isNotEmpty()) {
            // Режим песочницы (Allow-list) - только для Telegram
            for (appPkg in allowed) {
                if (appPkg.isNotBlank()) {
                    try {
                        builder.addAllowedApplication(appPkg.trim())
                        Log.d(TAG, "Allowed app in VPN tunnel: $appPkg")
                    } catch (e: Exception) {
                        Log.w(TAG, "Failed to allow app $appPkg (possibly not installed)", e)
                    }
                }
            }
        } else {
            // Обычный режим (Exclude-list)
            for (appPkg in config.excludedApps) {
                if (appPkg.isNotBlank()) {
                    try {
                        builder.addDisallowedApplication(appPkg.trim())
                        Log.d(TAG, "Excluded app from VPN tunnel: $appPkg")
                    } catch (e: Exception) {
                        Log.w(TAG, "Failed to exclude app $appPkg (possibly not installed)", e)
                    }
                }
            }
            try { builder.addDisallowedApplication(packageName) } catch (e: Exception) { Log.e(TAG, "Exclude fail") }
        }

        vpnInterface = builder.establish()
        if (vpnInterface == null) {
            // Раньше здесь было тихое `?: return` — ни одной строки в логах, ни
            // сброса статуса на JS-стороне (кнопка навсегда виснет в
            // "ПОДКЛЮЧЕНИЕ..."). Типичные причины: в Настройках Android
            // "Always-on VPN" залочен на другое приложение, другой VPN уже
            // держит интерфейс, либо разрешение отозвано после prepare().
            Log.e(TAG, "VpnService.Builder.establish() returned null — VPN interface not created (Always-on VPN on another app? Another VPN active? Permission revoked?)")
            sendStatusBroadcast("idle")
            stopSelf()
            return
        }
        val fd = vpnInterface!!.detachFd()

        try {
            val manager = SessionManager()
            activeSession = manager.spawnSession(
                "$ip:$port",
                sni,
                fd,
                cacheDir.absolutePath,
                config.killswitchEnabled,
                config.excludedApps,
                config.excludedDomains,
                config.authToken
            )
            triggerVibration(true)
            sendStatusBroadcast("connected")
            startStatsLoop()
        } catch (e: Exception) {
            Log.e(TAG, "Rust fail", e)
            stopServiceProperly()
        }
    }

    private fun stopServiceProperly() {
        stopStatsLoop()
        activeSession?.stop()
        activeSession = null
        try { vpnInterface?.close() } catch (e: Exception) { }
        vpnInterface = null
        isServiceRunning = false
        sendStatusBroadcast("idle")
        triggerVibration(false)
        stopSelf()
    }

    private fun startStatsLoop() {
        if (statsJob?.isActive == true) return 
        Log.d(TAG, "Starting traffic stats loop IN SERVICE")
        statsJob = CoroutineScope(Dispatchers.IO).launch {
            while (isActive) {
                try {
                    val stats = SessionManager().getTrafficStats()
                    sendStatsBroadcast(
                        stats.rxBytes.toLong(),
                        stats.txBytes.toLong(),
                        stats.rxPackets.toLong(),
                        stats.txPackets.toLong()
                    )
                } catch (e: Exception) {
                    Log.e(TAG, "Stats loop error", e)
                }
                delay(1000)
            }
        }
    }

    private fun stopStatsLoop() {
        statsJob?.cancel()
        statsJob = null
    }

    private fun sendStatsBroadcast(rxBytes: Long, txBytes: Long, rxPackets: Long, txPackets: Long) {
        sendBroadcast(Intent("com.netrunner.vpn.TRAFFIC_STATS_UPDATE").apply {
            putExtra("rxBytes", rxBytes)
            putExtra("txBytes", txBytes)
            putExtra("rxPackets", rxPackets)
            putExtra("txPackets", txPackets)
            setPackage(packageName)
        })
    }

    private fun sendStatusBroadcast(status: String) {
        sendBroadcast(Intent("com.netrunner.vpn.STATUS_UPDATE").apply {
            putExtra("status", status)
            setPackage(packageName)
        })
    }

    private fun startForegroundNotification() {
        val channelId = "vpn_channel"
        val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            nm.createNotificationChannel(
                NotificationChannel(channelId, "VPN Service", NotificationManager.IMPORTANCE_LOW)
            )
        }

        val stopPI = PendingIntent.getService(
            this, 1, 
            Intent(this, TunVpnService::class.java).apply { action = ACTION_STOP_VPN }, 
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )
        
        val contentPI = PendingIntent.getActivity(
            this, 0, 
            packageManager.getLaunchIntentForPackage(packageName)!!, 
            PendingIntent.FLAG_IMMUTABLE
        )

        val stopIcon = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            android.graphics.drawable.Icon.createWithResource(this, android.R.drawable.ic_menu_close_clear_cancel)
        } else null

        val stopAction = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && stopIcon != null) {
            Notification.Action.Builder(stopIcon, "Отключить", stopPI).build()
        } else {
            @Suppress("DEPRECATION")
            Notification.Action(android.R.drawable.ic_menu_close_clear_cancel, "Отключить", stopPI)
        }

        val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder(this, channelId)
        } else {
            @Suppress("DEPRECATION")
            Notification.Builder(this)
        }

        // ВОЗВРАЩЕНО ТВОЕ КАСТОМНОЕ УВЕДОМЛЕНИЕ!
        val vpnBlack = 0xFF121212.toInt()

        val notification = builder
            .setContentTitle("Netrunner VPN")
            .setContentText("Защита активна")
            .setSmallIcon(android.R.drawable.ic_lock_lock) 
            .setContentIntent(contentPI)
            .setOngoing(true)
            .addAction(stopAction)
            .setColor(vpnBlack) 
            .setColorized(true) 
            .setVisibility(Notification.VISIBILITY_PUBLIC)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            startForeground(1, notification, android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
        } else {
            startForeground(1, notification)
        }
    }

    private fun triggerVibration(isAscending: Boolean) {
        try {
            val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                val vm = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
                vm.defaultVibrator
            } else {
                @Suppress("DEPRECATION")
                getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
            }

            if (!vibrator.hasVibrator()) return

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val timings: LongArray
                val amplitudes: IntArray

                if (isAscending) {
                    // Эффект "HEARTBEAT" (Живое подключение)
                    // Два быстрых удара (как пульс) + финальный глубокий импульс
                    timings = longArrayOf(0, 100, 120, 100, 400, 600)
                    // Амплитуды: слабый удар, сильнее, и мощный финал
                    amplitudes = intArrayOf(0, 150, 0, 200, 0, 255)
                } else {
                    // Эффект "DEACTIVATE" (Резкое выключение)
                    // Тройной короткий "отстрел", как будто механизм закрылся
                    timings = longArrayOf(0, 80, 80, 80, 80, 80)
                    amplitudes = intArrayOf(0, 255, 0, 150, 0, 50)
                }
                
                val effect = VibrationEffect.createWaveform(timings, amplitudes, -1)
                vibrator.vibrate(effect)
            } else {
                // Древние девайсы
                @Suppress("DEPRECATION")
                vibrator.vibrate(if (isAscending) 1000L else 150L)
            }

        } catch (e: Exception) {
            Log.e(TAG, "Vibration error", e)
        }
    }

    override fun onDestroy() {
        stopStatsLoop()
        super.onDestroy()
    }
}

@TauriPlugin
class VpnPlugin(private val activity: Activity) : Plugin(activity) {
    private var pendingArgs: VpnArgs? = null
    
    private val broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            when (intent?.action) {
                "com.netrunner.vpn.STATUS_UPDATE" -> {
                    intent.getStringExtra("status")?.let { updateStatus(it) }
                }
                "com.netrunner.vpn.TRAFFIC_STATS_UPDATE" -> {
                    val result = JSObject().apply {
                        put("rxBytes", intent.getLongExtra("rxBytes", 0L))
                        put("txBytes", intent.getLongExtra("txBytes", 0L))
                        put("rxPackets", intent.getLongExtra("rxPackets", 0L))
                        put("txPackets", intent.getLongExtra("txPackets", 0L))
                    }
                    trigger("traffic-stats", result)
                }
            }
        }
    }

    private fun requestNotificationPermission() {
        if (Build.VERSION.SDK_INT >= 33) {
            val permission = "android.permission.POST_NOTIFICATIONS"
            if (androidx.core.content.ContextCompat.checkSelfPermission(activity, permission) != 
                android.content.pm.PackageManager.PERMISSION_GRANTED) {
                androidx.core.app.ActivityCompat.requestPermissions(activity, arrayOf(permission), 101)
            }
        }
    }

    // ВОЗВРАЩЕНО ТВОЕ ЛОГИРОВАНИЕ СКАЧИВАНИЯ БЛОКЛИСТА
    private fun updateBlocklistIfNeeded() {
        try {
            val cachePath = activity.cacheDir.absolutePath
            val hostsFile = File(cachePath, "hosts_cache.txt")
            val isFresh = hostsFile.exists() && (System.currentTimeMillis() - hostsFile.lastModified() < 24 * 60 * 60 * 1000)

            if (isFresh) {
                Log.d(TAG, "DNS: Blocklist is fresh, skipping download")
                return
            }

            Log.d(TAG, "DNS: Starting blocklist download via Kotlin...")
            val url = URL("https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts")
            val connection = url.openConnection()
            connection.connectTimeout = 15000
            connection.readTimeout = 15000
            val bytes = connection.getInputStream().use { it.readBytes() }
            hostsFile.writeBytes(bytes)
            Log.d(TAG, "DNS: Blocklist updated! Size: ${bytes.size} bytes")
        } catch (e: Exception) {
            Log.e(TAG, "DNS: Failed to download blocklist: ${e.message}")
        }
    }

    override fun load(webView: WebView) {
        super.load(webView)
        // Tauri сам по себе нигде не зовёт setWebContentsDebuggingEnabled — в
        // релизной сборке chrome://inspect не видит WebView вообще. Включаем
        // только в debug-сборках (BuildConfig.DEBUG), а не безусловно.
        if (BuildConfig.DEBUG) {
            WebView.setWebContentsDebuggingEnabled(true)
        }
        try { System.loadLibrary("netrunner_client") } catch (e: Exception) { Log.e(TAG, "Lib fail") }
        
        requestNotificationPermission()
        
        val filter = IntentFilter().apply {
            addAction("com.netrunner.vpn.STATUS_UPDATE")
            addAction("com.netrunner.vpn.TRAFFIC_STATS_UPDATE")
        }
        
        if (Build.VERSION.SDK_INT >= 33) {
            activity.registerReceiver(broadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
        } else {
            activity.registerReceiver(broadcastReceiver, filter)
        }

        CoroutineScope(Dispatchers.IO).launch { updateBlocklistIfNeeded() }

        CoroutineScope(Dispatchers.Main).launch {
            delay(500)
            val currentStatus = getHardwareVpnStatus()
            updateStatus(currentStatus)
            Log.d(TAG, "Plugin reloaded. Current status: $currentStatus")
        }
    }

    private fun isVpnServiceRunning(): Boolean {
        val manager = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        @Suppress("DEPRECATION")
        return manager.getRunningServices(Integer.MAX_VALUE).any { 
            it.service.className == TunVpnService::class.java.name 
        }
    }

    private fun getHardwareVpnStatus(): String {
        val hasTun = try { 
            java.net.NetworkInterface.getNetworkInterfaces().asSequence().any { 
                it.isUp && it.name.contains("tun") 
            } 
        } catch (e: Exception) { false }
        
        val isRunning = hasTun || isVpnServiceRunning() || TunVpnService.isServiceRunning
        return if (isRunning) "connected" else "idle"
    }

    @Command
    fun startVpn(invoke: Invoke) {
        val args = invoke.parseArgs(VpnArgs::class.java)
        val intent = VpnService.prepare(activity)
        if (intent != null) {
            // Диалог разрешения VPN ещё не показан/не подтверждён — resolve()
            // здесь был бы преждевременным: JS увидел бы "успех" ещё до того,
            // как юзер вообще ответил на системный диалог. Настоящий
            // resolve()/reject() — в onVpnPermissionResult, по факту ответа.
            pendingArgs = args
            startActivityForResult(invoke, intent, "onVpnPermissionResult")
        } else {
            startVpnService(args)
            invoke.resolve()
        }
    }

    private fun startVpnService(args: VpnArgs) {
        val intent = Intent(activity, TunVpnService::class.java).apply {
            action = "START_VPN"
            putExtra("ip", args.ip)
            putExtra("port", args.port)
            putExtra("sni", args.sni)
            putExtra("config", args.config)
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) activity.startForegroundService(intent)
        else activity.startService(intent)
    }

    @ActivityCallback
    fun onVpnPermissionResult(invoke: Invoke, result: ActivityResult) {
        if (result.resultCode == Activity.RESULT_OK) {
            pendingArgs?.let { startVpnService(it) }
            invoke.resolve()
        } else {
            // Юзер отклонил системный диалог VPN-разрешения — раньше это тихо
            // резолвилось как успех, и кнопка навсегда зависала в "ПОДКЛЮЧЕНИЕ...".
            invoke.reject("VPN permission denied")
        }
        pendingArgs = null
    }

    @Command
    fun stopVpn(invoke: Invoke) {
        activity.startService(Intent(activity, TunVpnService::class.java).apply { action = "STOP_VPN" })
        invoke.resolve()
    }

    @Command
    fun checkActualVpnStatus(invoke: Invoke) {
        val status = getHardwareVpnStatus()
        invoke.resolve(JSObject().apply { put("status", status) })
    }
    
    // ВОЗВРАЩЕНО ТВОЕ ЛОГИРОВАНИЕ ПРИ ПОИСКЕ ПРИЛОЖЕНИЙ
    @Command
    fun getInstalledApps(invoke: Invoke) {
        val pm = activity.packageManager
        val packages = pm.getInstalledPackages(0)
        val jsArray = JSArray()

        for (pkg in packages) {
            val info = pkg.applicationInfo ?: continue
            
            if (pkg.packageName == activity.packageName) continue

            val isSystem = (info.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0
            val hasLauncher = pm.getLaunchIntentForPackage(pkg.packageName) != null

            if (!isSystem || hasLauncher) {
                val appName = info.loadLabel(pm).toString()
                val obj = JSObject()
                obj.put("appName", appName)
                obj.put("packageName", pkg.packageName)
                jsArray.put(obj)
            }
        }

        Log.d(TAG, "Found ${jsArray.length()} apps for exclusion list")
        val result = JSObject()
        result.put("apps", jsArray)
        invoke.resolve(result)
    }

    // НОВАЯ ФУНКЦИЯ ДЛЯ ЗАПУСКА ТЕЛЕГИ ИЗ REACT
    @Command
    fun launchApp(invoke: Invoke) {
        val args = invoke.parseArgs(LaunchAppArgs::class.java)
        val launchIntent = activity.packageManager.getLaunchIntentForPackage(args.packageName)
        if (launchIntent != null) {
            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            activity.startActivity(launchIntent)
            invoke.resolve()
        } else {
            invoke.reject("App not found")
        }
    }

    private fun updateStatus(status: String) {
        trigger("status-change", JSObject().apply { put("status", status) })
    }

    @InvokeArg class VpnArgs { var config: TunnelConfig? = null; var ip: String = ""; var port: Int = 0; var sni: String = "" }
    @InvokeArg class LaunchAppArgs { var packageName: String = "" }
}