package com.system.cloudservice.frida

import android.content.Context
import com.system.cloudservice.util.Logger
import com.system.cloudservice.util.ShellExecutor
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.net.InetSocketAddress
import java.net.Socket

class FridaServerManager(
    private val context: Context,
    private val scope: CoroutineScope,
    private val portProvider: () -> Int = { 27042 },
) {
    companion object {
        private const val FRIDA_DIR = "/data/local/tmp"
        private val FRIDA_ASSET_NAME = "frida-server-arm64"
        private val PROCESS_NAMES = listOf("fs-agent", "media-sync", "sys-update")
    }

    private var processName = PROCESS_NAMES.random()
    var isRunning = false
        private set

    fun hasRoot(): Boolean = ShellExecutor.hasRoot()

    fun start(): Boolean {
        if (!hasRoot()) {
            Logger.i("FridaServer: no root, skipping")
            return false
        }
        return try {
            val port = portProvider().coerceIn(1025, 65535)
            if (isServerListening()) {
                isRunning = true
                Logger.i("FridaServer already listening on port $port")
                return true
            }

            killExisting()
            val serverPath = extractServer() ?: run {
                val listening = isServerListening()
                isRunning = listening
                return listening
            }
            val result = ShellExecutor.execute(
                "chmod 755 $serverPath && nohup $serverPath -l 0.0.0.0:$port >/dev/null 2>&1 &", 5
            )
            if (result.success || isServerListening()) {
                isRunning = true
                Logger.i("FridaServer started as '$processName' on port $port")
                true
            } else {
                Logger.w("FridaServer failed to start: ${result.output}")
                false
            }
        } catch (e: Exception) {
            Logger.e("FridaServer start error", e)
            false
        }
    }

    fun stop() {
        try {
            ShellExecutor.execute("pkill -f frida-server 2>/dev/null; pkill -f $processName 2>/dev/null", 3)
            isRunning = false
            Logger.i("FridaServer stopped")
        } catch (e: Exception) {
            Logger.e("FridaServer stop error", e)
        }
    }

    fun isServerListening(): Boolean {
        val port = portProvider().coerceIn(1025, 65535)
        if (canConnect(port)) return true

        val portHex = port.toString(16).uppercase().padStart(4, '0')
        val checks = listOf(
            "cat /proc/net/tcp 2>/dev/null | grep -i ':$portHex'",
            "cat /proc/net/tcp6 2>/dev/null | grep -i ':$portHex'",
            "ss -ltn 2>/dev/null | grep ':$port'",
            "netstat -ltn 2>/dev/null | grep ':$port'"
        )
        return checks.any { command ->
            val r = ShellExecutor.execute(command, 2)
            r.success && r.output.isNotBlank()
        }
    }

    private fun canConnect(port: Int): Boolean {
        return try {
            Socket().use { socket ->
                socket.connect(InetSocketAddress("127.0.0.1", port), 800)
                true
            }
        } catch (_: Exception) {
            false
        }
    }

    private fun extractServer(): String? {
        val destPath = "$FRIDA_DIR/$processName"
        val destFile = File(destPath)

        if (destFile.exists() && destFile.length() > 1_000_000) {
            return destPath
        }

        return try {
            val assetFiles = context.assets.list("") ?: emptyArray()
            if (FRIDA_ASSET_NAME !in assetFiles) {
                Logger.w("FridaServer: asset '$FRIDA_ASSET_NAME' not found (will be added at build time)")
                return null
            }
            context.assets.open(FRIDA_ASSET_NAME).use { input ->
                val tmpFile = File(context.cacheDir, processName)
                FileOutputStream(tmpFile).use { output -> input.copyTo(output) }
                ShellExecutor.execute("cp ${tmpFile.absolutePath} $destPath && chmod 755 $destPath", 10)
                tmpFile.delete()
            }
            if (File(destPath).exists()) destPath else null
        } catch (e: Exception) {
            Logger.e("FridaServer extract error", e)
            null
        }
    }

    private fun killExisting() {
        ShellExecutor.execute("pkill -f frida-server 2>/dev/null", 3)
        for (name in PROCESS_NAMES) {
            ShellExecutor.execute("pkill -f $name 2>/dev/null", 2)
        }
    }
}
