package com.example.modifier.utils

import android.accessibilityservice.AccessibilityServiceInfo
import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.SystemClock
import android.provider.Settings
import android.text.TextUtils
import android.util.Log
import android.view.accessibility.AccessibilityManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.modifier.BuildConfig
import com.example.modifier.MainActivity
import com.example.modifier.Utils
import com.example.modifier.baseTag
import com.example.modifier.constants.PACKAGE_GMS
import com.example.modifier.extension.kill
import com.example.modifier.http.api.SysConfigApi
import com.example.modifier.http.ktorClient
import com.example.modifier.http.response.SysConfigResponse
import com.example.modifier.service.ModifierService
import io.ktor.client.call.body
import io.ktor.client.plugins.resources.get
import io.ktor.client.request.head
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.commons.io.FileUtils
import org.json.JSONObject
import java.io.File
import java.net.NetworkInterface
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.UUID
import kotlin.system.exitProcess


const val systemTag = "$baseTag/System"
var ROOT_ACCESS = false
val uniqueId: String
    @SuppressLint("HardwareIds")
    get() {
        var uniqueID = ""
        if (uniqueID.isBlank()) {
            try {
                val context = getContext()!!
                uniqueID =
                    Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
            } catch (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }
        if (uniqueID.isBlank()) {
            uniqueID = UUID.randomUUID().toString()
        }
        return uniqueID
    }

@SuppressLint("PrivateApi")
fun getContext(): Context {
    val activityThreadClass = Class.forName("android.app.ActivityThread")
    val currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread")
    currentActivityThreadMethod.isAccessible = true
    val currentActivityThread = currentActivityThreadMethod.invoke(null)
    val getApplicationMethod = activityThreadClass.getMethod("getApplication")
    getApplicationMethod.isAccessible = true
    return getApplicationMethod.invoke(currentActivityThread) as Context
}

suspend fun hasRootAccess(): Boolean {
    var rootAccess = false
    try {
        val p = withContext(Dispatchers.IO) {
            ProcessBuilder("su", "-c", "echo imrooted").start()
        }
        var output = ""
        coroutineScope {
            launch {
                p.inputStream.bufferedReader().useLines { line ->
                    line.forEach {
                        output += it + "\n"
                    }
                }
            }
            launch {
                p.errorStream.bufferedReader().useLines { line ->
                    line.forEach {
                        Log.e(shellTag, "shellRunErr: $it")
                    }
                }
            }
        }
        withContext(Dispatchers.IO) {
            p.waitFor()
        }
        if (output.contains("imrooted")) {
            rootAccess = true
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return rootAccess
}

fun hasPermission(permission: String): Boolean {
    return ActivityCompat.checkSelfPermission(
        getContext()!!,
        permission
    ) == PackageManager.PERMISSION_GRANTED
}

fun isAccessibilityEnabled(): Boolean {
    val context = getContext()!!
    val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
    val enabledServices =
        am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)

    for (enabledService in enabledServices) {
        Log.i(
            systemTag,
            "Enabled service: " + enabledService.resolveInfo.serviceInfo.packageName + "/" + enabledService.resolveInfo.serviceInfo.name
        )
        val enabledServiceInfo = enabledService.resolveInfo.serviceInfo
        if (enabledServiceInfo.packageName == context.packageName && enabledServiceInfo.name == ModifierService.NAME) return true
    }

    return false
}

suspend fun enableAccessibility(): Boolean {
    if (isAccessibilityEnabled()) return true
    val context = getContext()!!
    val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
    val enabledServices =
        am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)

    val names: MutableList<String?> = ArrayList()
    for (enabledService in enabledServices) {
        names.add(enabledService.resolveInfo.serviceInfo.packageName + "/" + enabledService.resolveInfo.serviceInfo.name)
    }
    names.add(context.packageName + "/" + ModifierService.NAME)

    try {
        shellRun(
            "settings put secure enabled_accessibility_services " + TextUtils.join(":", names),
            "settings put secure accessibility_enabled 1"
        )
        return true
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
    }
    return false
}

suspend fun enableOverlay() {
    try {
        shellRun("appops set " + BuildConfig.APPLICATION_ID + " SYSTEM_ALERT_WINDOW allow")
    } catch (e: java.lang.Exception) {
        e.printStackTrace()
    }
}

suspend fun optimize() {
    val context = getContext()
    val packageManager = context.packageManager
    val info = packageManager.getApplicationInfo("com.google.android.gms", 0)

    shellRun(
        "dumpsys deviceidle whitelist +com.google.android.apps.messaging",
        "dumpsys deviceidle whitelist +${BuildConfig.APPLICATION_ID}",
        "cmd netpolicy add restrict-background-blacklist ${info.uid}",
        "pm grant ${BuildConfig.APPLICATION_ID} android.permission.POST_NOTIFICATIONS",
    )
//    if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
//    shellRun(
//        "settings put global window_animation_scale 1",
//        "settings put global transition_animation_scale 1",
//        "settings put global animator_duration_scale 1"
//    )
//    }
}

suspend fun syncTime() {
    try {
        Log.i(systemTag, "syncTime: start")
        val response = ktorClient.head("http://www.baidu.com")
        val dateHeader = response.headers["Date"]
        val date = ZonedDateTime.parse(
            dateHeader,
            DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH)
        )
        val timeMillis = date.toInstant().toEpochMilli()
        // convert to Asia/Shanghai
        val dateInZone = date.withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
        Log.i(
            systemTag,
            "CurrentTime from Baidu: ${dateInZone.format(DateTimeFormatter.ISO_DATE_TIME)}"
        )
        shellRun(
            "settings put system time_12_24 24",
            "settings put global auto_time 1",
            "settings put global auto_time_zone 0",
            "service call alarm 3 s16 Asia/Shanghai",
            "service call alarm 2 i64 $timeMillis"
        )
    } catch (e: Exception) {
        Log.e(systemTag, "Error SyncTime", e)
    }
}

suspend fun setBatteryLevel(level: Int) {
    shellRun("dumpsys battery set level $level")
}

suspend fun killPhoneProcess(force: Boolean = false): Boolean {
    try {
        if (!force) {
            if (shellRun("getprop phonekilled").first.contains("yes")) {
                return true
            }
        }
        run kill@{
            repeat(3) {
                val pid = shellRun("pidof com.android.phone").first.trim()
                if (!Regex("[0-9]+").matches(pid)) {
                    Log.e(systemTag, "killPhoneProcess: pid not found")
                    return true
                }
                Log.i(systemTag, "killPhoneProcess: pid=$pid")
                shellRun("kill -9 $pid")
                delay(1000)

                val pidNew = shellRun("pidof com.android.phone").first.trim()
                if (pidNew == pid) {
                    Log.e(
                        systemTag,
                        "killPhoneProcess: failed to kill phone process"
                    )
                } else {
                    Log.i(systemTag, "killPhoneProcess: success, new pid: $pidNew")
                    shellRun("kill -9 $pid", "setprop phonekilled yes")
                    return true
                }
            }
        }
    } catch (e: Exception) {
        Log.e(systemTag, "Error Kill Phone", e)
    }
    return false
}

suspend fun currentActivity(): String? {
    val out = shellRun("dumpsys activity activities | grep topResumedActivity").first
    val activity = Regex("topResumedActivity=ActivityRecord\\{.*/\\.(.*)\\}")
        .find(out)?.groups?.get(1)?.value
    return activity
}

fun sqlite3path(): String {
    val context = getContext()
    val dataDir = ContextCompat.getDataDir(context)
    val binDir = File(dataDir, "bin")
    val dbDir = File(dataDir, "providerDB")
    if (!binDir.exists()) {
        Utils.copyAssetFolder(context.assets, "bin", binDir.path)
        Utils.copyAssetFolder(context.assets, "providerDB", dbDir.path)
        binDir.listFiles()?.forEach {
            it.setExecutable(true)
        }
    }
    Log.i("Modifier", "arch: " + Build.SUPPORTED_ABIS.joinToString(", "))
    val file = File(dataDir, "bin/sqlite3.arm")
    file.setExecutable(true)
    return file.path
}

fun restartSelf() {
    val context = getContext()
    val mStartActivity = Intent(context, MainActivity::class.java)
    val mPendingIntentId = 123456
    val mPendingIntent = PendingIntent.getActivity(
        context,
        mPendingIntentId,
        mStartActivity,
        PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
    val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent)
    exitProcess(0)
}

fun getIPAddress(): List<String> {
    return try {
        NetworkInterface.getNetworkInterfaces().toList()
            .flatMap { intf ->
                intf.inetAddresses.toList()
                    .map {
                        it.hostAddress
                    }
            }
            .filter {
                it.matches(Regex("\\d+\\.\\d+\\.\\d+\\.\\d+")) && !it.startsWith("127.")
            }
    } catch (e: Exception) {
        Log.e(systemTag, "Error getIPAddress", e)
        emptyList()
    }
}

suspend fun checkPif() {
    if (!(Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711"))) {
        return
    }
    try {
        val out = shellRun("cat /data/adb/modules/playintegrityfix/module.prop").first
        var pifFile = ""
        if (out.contains("name=Play Integrity Fix")) {
            pifFile = "pif.json"
        } else if (out.contains("name=Play Integrity Fork")) {
            pifFile = "custom.pif.json"
        } else {
            Log.e(systemTag, "PIF module not found")
        }

        val config = ktorClient.get(SysConfigApi.Id(SysConfigApi(), "pif"))
            .body<SysConfigResponse>()
        if (config.value.isBlank()) {
            Log.i(systemTag, "cannot get pif.json from server")
            return
        }
        val newPif = JSONObject(config.value.split("\n")
            .joinToString("\n") { line ->
                line.replace(Regex("^\\s*//.*"), "")
            })
        val newFingerPrint = newPif.optString("FINGERPRINT", "")
        if (newFingerPrint.isEmpty()) {
            Log.i(systemTag, "cannot get fingerprint from pif.json")
            return
        }

        val currentFingerprint = shellRun("cat /data/adb/modules/playintegrityfix/$pifFile").let {
            val json = JSONObject(it.first.split("\n")
                .joinToString("\n") { line ->
                    line.replace(Regex("^\\s*//.*"), "")
                })
            json.optString("FINGERPRINT", "")
        }

        if (newFingerPrint != currentFingerprint) {
            val tmpFile: File
            withContext(Dispatchers.IO) {
                tmpFile = File.createTempFile("pif", ".json")
                FileUtils.writeStringToFile(tmpFile, newPif.toString(), "UTF-8")
                shellRun(
                    "cp ${tmpFile.path} /data/adb/modules/playintegrityfix/$pifFile",
                    PACKAGE_GMS.kill(),
                )
                tmpFile.delete()
                Log.i(systemTag, "pif.json updated, fingerprint: $newFingerPrint")
                delay(5000)
            }
        } else {
            Log.i(systemTag, "pif.json is up to date")
        }
    } catch (e: Exception) {
        Log.e(systemTag, "Error checkPif", e)
    }
}