package com.example.modifier.repo

import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.core.content.ContextCompat
import com.example.modifier.baseTag
import com.example.modifier.constants.CMD_MESSAGING_APP
import com.example.modifier.constants.CMD_RCS_SETTINGS_ACTIVITY
import com.example.modifier.constants.PACKAGE_GMS
import com.example.modifier.constants.PACKAGE_GSF
import com.example.modifier.constants.PACKAGE_MESSAGING
import com.example.modifier.data.BackupItem
import com.example.modifier.data.BackupItemDao
import com.example.modifier.enums.RcsConfigureState
import com.example.modifier.enums.ReqState
import com.example.modifier.extension.clear
import com.example.modifier.extension.disable
import com.example.modifier.extension.enable
import com.example.modifier.extension.kill
import com.example.modifier.http.api.RcsNumberApi
import com.example.modifier.model.SpoofedSimInfo
import com.example.modifier.service.ModifierService
import com.example.modifier.utils.ROOT_ACCESS
import com.example.modifier.utils.clearConv
import com.example.modifier.utils.genMacAddress
import com.example.modifier.utils.genSerialNo
import com.example.modifier.utils.getContext
import com.example.modifier.utils.shellRun
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
import java.util.Date
import java.util.Optional
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration.Companion.minutes

class BackupRepository(
    private val context: Context,
    private val dao: BackupItemDao,
) {
    companion object {
        private const val TAG = "$baseTag/BackupRepository"
    }

    suspend fun backup(type: String, sendCount: Int, stock: Int = 0): BackupItem? {
        if (!ROOT_ACCESS) return null
        AppStateRepo.instance.updateRuntimeFlags(reqState = ReqState.BACKUP)
        val spoofedInfoRepo = SpoofedInfoRepo.instance
        clearConv()
        delay(3000)
//        ModifierService.instance!!.toggleRcsSwitch(false)
        val context = getContext()
        val dest = File(
            ContextCompat.getExternalFilesDirs(context, "backup")[0],
            System.currentTimeMillis().toString()
        )
        dest.mkdirs()

        val packages = mutableListOf(
            PACKAGE_MESSAGING,
            PACKAGE_GMS,
            PACKAGE_GSF
        )
        packages.forEach {
            File(dest, it).mkdirs()
        }

        val cmds = mutableListOf<String>()

        for (pkg in packages) {
            if (!shellRun("ls /data/data/$pkg").second
                    .contains("No such file or directory")
            ) {
                cmds.add("tar zcpf $dest/$pkg/data.tar.gz -C /data/data $pkg ")
            }
            if (!shellRun("ls /data/user_de/0/$pkg").second
                    .contains("No such file or directory")
            ) {
                cmds.add("tar zcpf $dest/$pkg/data_de.tar.gz -C /data/user_de/0 $pkg ")
            }
            if (!shellRun("ls /sdcard/Android/data/$pkg").second
                    .contains("No such file or directory")
            ) {
                cmds.add("tar zcpf $dest/$pkg/data_ext.tar.gz -C /sdcard/Android/data $pkg ")
            }
        }

        shellRun(*cmds.toTypedArray())

        val spoofedSimInfo = spoofedInfoRepo.spoofedSimInfo.value
        val backup = BackupItem(
            createdAt = Date().time,
            numberId = spoofedSimInfo.numberId,
            number = spoofedSimInfo.number,
            code = spoofedSimInfo.areaCode,
            country = spoofedSimInfo.country,
            mcc = spoofedSimInfo.mcc,
            mnc = spoofedSimInfo.mnc,
            imei = spoofedSimInfo.imei,
            imsi = spoofedSimInfo.imsi,
            iccid = spoofedSimInfo.iccid,
            path = dest.path,
            sendCount = sendCount,
            lastUse = Date().time,
            type = type,
            stock = stock,
            carrierId = Optional.ofNullable(spoofedSimInfo.carrierId).orElse("1"),
            carrierName = Optional.ofNullable(spoofedSimInfo.carrierName).orElse("T-Mobile"),
            retry = 0
        )

        dao.findBackupForNumber(spoofedSimInfo.country, spoofedSimInfo.number)?.let {
            File(it.path).deleteRecursively()
            backup.sendCount += it.sendCount
            backup.stock = it.stock
            dao.delete(it)
        }

        backup.id = dao.insert(backup).toInt()
        return backup
    }

    @SuppressLint("SdCardPath")
    suspend fun restore(backup: BackupItem): Boolean {
        val spoofedInfoRepo = SpoofedInfoRepo.instance
        val simInfo = SpoofedSimInfo(
            numberId = backup.numberId,
            number = backup.number,
            mcc = backup.mcc,
            mnc = backup.mnc,
            country = backup.country,
            areaCode = backup.code,
            iccid = backup.iccid,
            imei = backup.imei,
            imsi = backup.imsi,
            available = false,
            carrierId = backup.carrierId,
            carrierName = backup.carrierName,
            serialNo = genSerialNo(),
            wifiMac = genMacAddress(),
            bssid = genMacAddress(),
            btMac = genMacAddress(),
            ethMac = genMacAddress()
        )
        spoofedInfoRepo.updateSpoofedSimInfo(spoofedSimInfo = simInfo, suspend = false)
        val packages = mutableListOf(
            PACKAGE_MESSAGING,
            PACKAGE_GMS,
            PACKAGE_GSF
        )

        shellRun(PACKAGE_MESSAGING.clear(), "sleep 2", PACKAGE_MESSAGING.disable())
        for (pkg in packages) {
            for (item in listOf("data.tar.gz", "data_de.tar.gz", "data_ext.tar.gz")) {
                val file = File("${backup.path}/$pkg/$item")
                if (!file.exists()) {
                    continue
                }
                val dest = when (item) {
                    "data.tar.gz" -> "/data/data"
                    "data_de.tar.gz" -> "/data/user_de/0"
                    "data_ext.tar.gz" -> "/sdcard/Android/data"
                    else -> ""
                }
                shellRun(
                    "find $dest/$pkg -type f -delete",
                    "tar zxf ${file.path} -C $dest --exclude \"**/AndroidPlatformServices.apk\""
                )
            }
        }

        val taskRunner = ModifierService.instance?.taskRunner
        shellRun(
//            "sed -i 's/<boolean name=\"enable_rcs\" value=\"true\" \\/>/<boolean name=\"enable_rcs\" value=\"false\" \\/>/g' /data/data/com.google.android.apps.messaging/shared_prefs/bugle.xml",
            PACKAGE_GSF.kill(),
            "sleep 1",
            PACKAGE_GMS.kill(),
            "settings put secure location_mode 0",
            "pm revoke com.google.android.gms android.permission.GET_ACCOUNTS",
            "pm revoke com.google.android.gms android.permission.SYSTEM_ALERT_WINDOW",
            "pm revoke com.google.android.gms android.permission.POST_NOTIFICATIONS",
            "pm revoke com.google.android.gms android.permission.READ_CONTACTS",
            "pm revoke com.google.android.gms android.permission.CAMERA",
            "pm revoke com.google.android.gms android.permission.RECEIVE_MMS",
            "pm revoke com.google.android.gms android.permission.GET_APP_OPS_STATS",
            "pm revoke com.google.android.gms android.permission.PROCESS_OUTGOING_CALLS",
            "pm revoke com.google.android.gms android.permission.BLUETOOTH_CONNECT",
            "pm revoke com.google.android.gms android.permission.BLUETOOTH_SCAN",
            "pm revoke com.google.android.gms android.permission.BLUETOOTH_ADVERTISE",
            "pm revoke com.google.android.gms android.permission.NEARBY_WIFI_DEVICES",
            "pm revoke com.google.android.gms android.permission.UWB_RANGING",
            "pm revoke com.google.android.gms android.permission.READ_CALL_LOG",
            "pm revoke com.google.android.gms android.permission.WRITE_CONTACTS",
            "pm revoke com.google.android.gms android.permission.CALL_PHONE",
            "pm revoke com.google.android.gms android.permission.RECORD_AUDIO",
            "pm revoke com.google.android.gms android.permission.READ_LOGS",
            "pm revoke com.google.android.gms android.permission.READ_MEDIA_AUDIO",
            "pm revoke com.google.android.gms android.permission.READ_MEDIA_IMAGES",
            "pm revoke com.google.android.gms android.permission.READ_MEDIA_VIDEO",
            "pm revoke com.google.android.gms android.permission.ACCESS_MEDIA_LOCATION",
            "pm revoke com.google.android.gms android.permission.ACCESS_BROADCAST_RESPONSE_STATS",
            "pm revoke com.google.android.gms android.permission.WRITE_CALL_LOG",
            "pm revoke com.google.android.gms android.permission.BODY_SENSORS",
            "pm revoke com.google.android.gms android.permission.DUMP",
            "sleep 30",
            PACKAGE_MESSAGING.enable(),
            "sleep 1",
            CMD_MESSAGING_APP,
            "sleep 5",
            CMD_RCS_SETTINGS_ACTIVITY,
            "sleep 5",
            PACKAGE_MESSAGING.kill(),
            "sleep 1",
            CMD_MESSAGING_APP,
            "sleep 5",
        )
        var otp = false
        val success = run isSuccess@{
            if (backup.stock != 1) {
                return@isSuccess true
            }

            if (taskRunner == null) return@isSuccess false
            if (!taskRunner.screenController.toggleRcsSwitch(true)) {
                shellRun(
                    PACKAGE_GMS.kill(),
                    PACKAGE_MESSAGING.kill(),
                    "sleep 5",
                    CMD_MESSAGING_APP
                )
                delay(5000)
                if (!taskRunner.screenController.toggleRcsSwitch(true)) {
                    return@isSuccess false
                }
            }

            val job = CoroutineScope(coroutineContext).launch {
                taskRunner.gmsgStateRepo.rcsConfigureState.collect {
                    if (it == RcsConfigureState.RETRY) {
                        shellRun(
                            PACKAGE_GMS.kill(),
                            "sleep 5",
                            PACKAGE_MESSAGING.kill(),
                            CMD_MESSAGING_APP
                        )
                    }
                }
            }

            val state = taskRunner.gmsgStateRepo.waitForRcsState(
                arrayOf(RcsConfigureState.CONFIGURED, RcsConfigureState.WAITING_FOR_OTP), 5.minutes
            )
            job.cancel()
            if (state == RcsConfigureState.CONFIGURED) return@isSuccess true
            if (state == RcsConfigureState.WAITING_FOR_OTP) otp = true
            false
        }

        if (success) {
            Log.i(TAG, "restore: success")
            spoofedInfoRepo.updateAvailable(available = true)
            if (backup.stock == 1) {
                backup.stock = 2
                dao.update(backup)
                RcsNumberApi.updateStockFlag(backup.numberId, 2)
            }
            return true
        } else {
            Log.i(TAG, "restore: failed")
            if (backup.stock == 1) {
                backup.retry++
                if (backup.retry >= 3 || otp) {
                    RcsNumberApi.updateStockFlag(backup.numberId, 3)
                    if (otp) {
                        dao.delete(backup)
                    } else {
                        backup.stock = 3
                        dao.update(backup)
                    }
                } else {
                    dao.update(backup)
                }
            }
        }
        return false
    }

    suspend fun delete(backupItem: BackupItem) {
        dao.delete(backupItem)
    }

    suspend fun findBackupForRestore(number: String): BackupItem? {
        return dao.findBackupForRestore(number)
    }

    suspend fun cleanBackup(start: Long, end: Long) {
        dao.findBackupsBetween(start, end).forEach {
            runCatching {
                File(it.path).deleteRecursively()
            }
            dao.delete(it)
        }
    }
}