package com.example.modifier.repo

import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.util.Base64
import android.util.Log
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.example.modifier.BuildConfig
import com.example.modifier.baseTag
import com.example.modifier.constants.PACKAGE_GMS
import com.example.modifier.constants.PACKAGE_MESSAGING
import com.example.modifier.constants.SIMView
import com.example.modifier.extension.kill
import com.example.modifier.model.SpoofedSimInfo
import com.example.modifier.utils.ApduChannel
import com.example.modifier.utils.ROOT_ACCESS
import com.example.modifier.utils.SimEncoder
import com.example.modifier.utils.genICCID
import com.example.modifier.utils.genIMEI
import com.example.modifier.utils.genIMSI
import com.example.modifier.utils.genMacAddress
import com.example.modifier.utils.genSerialNo
import com.example.modifier.utils.getContext
import com.example.modifier.utils.hasPermission
import com.example.modifier.utils.isOldVersion
import com.example.modifier.utils.shellRun
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.apache.commons.io.FileUtils
import org.json.JSONObject
import java.io.File
import java.net.Socket
import java.nio.file.Files

val Context.simInfoDataStore by preferencesDataStore(name = "${BuildConfig.APPLICATION_ID}.simInfo")

class SpoofedSimInfoRepo private constructor(private val context: Context) {

    companion object {
        private const val TAG = "$baseTag/SpoofedSimInfoRepository"
        val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { SpoofedSimInfoRepo(getContext()) }
    }

    public object PreferencesKeys {
        val NUMBER_ID = intPreferencesKey("number_id")
        val NUMBER = stringPreferencesKey("number")
        val MCC = stringPreferencesKey("mcc")
        val MNC = stringPreferencesKey("mnc")
        val ICCID = stringPreferencesKey("iccid")
        val IMSI = stringPreferencesKey("imsi")
        val IMEI = stringPreferencesKey("imei")
        val COUNTRY = stringPreferencesKey("country")
        val AREA_CODE = stringPreferencesKey("area_code")
        val AVAILABLE = booleanPreferencesKey("available")
        val CARRIER_ID = stringPreferencesKey("carrier_id")
        val CARRIER_NAME = stringPreferencesKey("carrier_name")
        val SERIAL_NO = stringPreferencesKey("serial_no")
        val MAC = stringPreferencesKey("mac")
        val BSSID = stringPreferencesKey("bssid")
    }

    private val simInfoFlow = context.simInfoDataStore.data.map {
        val numberId = it[PreferencesKeys.NUMBER_ID] ?: 0
        val number = it[PreferencesKeys.NUMBER] ?: ""
        val mcc = it[PreferencesKeys.MCC] ?: ""
        val mnc = it[PreferencesKeys.MNC] ?: ""
        val iccid = it[PreferencesKeys.ICCID] ?: ""
        val imsi = it[PreferencesKeys.IMSI] ?: ""
        val imei = it[PreferencesKeys.IMEI] ?: ""
        val country = it[PreferencesKeys.COUNTRY] ?: ""
        val areaCode = it[PreferencesKeys.AREA_CODE] ?: ""
        val available = it[PreferencesKeys.AVAILABLE] ?: false
        val carrierId = it[PreferencesKeys.CARRIER_ID] ?: ""
        val carrierName = it[PreferencesKeys.CARRIER_NAME] ?: ""
        val serialNo = it[PreferencesKeys.SERIAL_NO] ?: ""
        val mac = it[PreferencesKeys.MAC] ?: ""
        val bssid = it[PreferencesKeys.BSSID] ?: ""
        SpoofedSimInfo(
            numberId = numberId,
            number = number,
            mcc = mcc,
            mnc = mnc,
            iccid = iccid,
            imsi = imsi,
            imei = imei,
            country = country,
            areaCode = areaCode,
            available = available,
            carrierId = carrierId,
            carrierName = carrierName,
            serialNo = serialNo,
            mac = mac,
            bssid = bssid
        )
    }

    val spoofedSimInfo = MutableStateFlow(
        SpoofedSimInfo(
            numberId = 0,
            number = "",
            mcc = "",
            mnc = "",
            iccid = "",
            imsi = "",
            imei = "",
            country = "",
            areaCode = "",
            available = false,
            carrierId = "",
            carrierName = "",
            serialNo = "",
            mac = "",
            bssid = "",
        )
    )

    init {
        CoroutineScope(Dispatchers.IO).launch {
            simInfoFlow.collect {
                spoofedSimInfo.emit(it)
            }
        }
    }

    @SuppressLint("MissingPermission")
    suspend fun updateSpoofedSimInfo(spoofedSimInfo: SpoofedSimInfo, suspend: Boolean? = true) {
        context.getSharedPreferences("share_sim_info", Context.MODE_PRIVATE)
            ?.edit()
            ?.putString("imei", spoofedSimInfo.imei)
            ?.apply()
        context.simInfoDataStore.edit {
            it[PreferencesKeys.NUMBER_ID] = spoofedSimInfo.numberId
            it[PreferencesKeys.NUMBER] = spoofedSimInfo.number
            it[PreferencesKeys.MCC] = spoofedSimInfo.mcc
            it[PreferencesKeys.MNC] = spoofedSimInfo.mnc
            it[PreferencesKeys.ICCID] = spoofedSimInfo.iccid
            it[PreferencesKeys.IMSI] = spoofedSimInfo.imsi
            it[PreferencesKeys.IMEI] = spoofedSimInfo.imei
            it[PreferencesKeys.COUNTRY] = spoofedSimInfo.country
            it[PreferencesKeys.AREA_CODE] = spoofedSimInfo.areaCode
            it[PreferencesKeys.AVAILABLE] = spoofedSimInfo.available
            it[PreferencesKeys.CARRIER_ID] = spoofedSimInfo.carrierId
            it[PreferencesKeys.CARRIER_NAME] = spoofedSimInfo.carrierName
            it[PreferencesKeys.SERIAL_NO] = spoofedSimInfo.serialNo
            it[PreferencesKeys.MAC] = spoofedSimInfo.mac
            it[PreferencesKeys.BSSID] = spoofedSimInfo.bssid
        }
        try {
            val jsonObject = JSONObject().apply {
                put("mcc", spoofedSimInfo.mcc)
                put("mnc", spoofedSimInfo.mnc)
                put("iccid", spoofedSimInfo.iccid)
                put("imsi", spoofedSimInfo.imsi)
                put("imei", spoofedSimInfo.imei)
                put("number", spoofedSimInfo.number)
                put("country", spoofedSimInfo.country)
                put("carrierId", spoofedSimInfo.carrierId)
                put("carrierName", spoofedSimInfo.carrierName)
                put("serialNo", spoofedSimInfo.serialNo)
                put("mac", spoofedSimInfo.mac)
                put("bssid", spoofedSimInfo.bssid)
            }
            runCatching {
                val jsonFile = File.createTempFile(
                    "temp",
                    ".json",
                    getContext().externalCacheDir
                )
                FileUtils.writeStringToFile(jsonFile, jsonObject.toString(), Charsets.UTF_8)
                shellRun(
                    "cp ${jsonFile.absolutePath} /sdcard/Android/data/com.android.phone/files/config/config.json",
                    "chown radio /sdcard/Android/data/com.android.phone/files/config/config.json",
                    "cp ${jsonFile.absolutePath} /data/system/config.json",
                    "chown system /data/system/config.json"
                )
                jsonFile.delete()
            }.onFailure {
                Log.e(TAG, "Error updateSpoofedSimInfo: ${it.message}", it)
            }
            if (suspend == true) {
                shellRun(
                    PACKAGE_GMS.kill(),
                    PACKAGE_MESSAGING.kill(),
                )
            }
            val context = getContext()
            val subscriptionManager: SubscriptionManager =
                context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager

            runCatching {
                val plmn = spoofedSimInfo.mcc + spoofedSimInfo.mnc
                val plmnHex = SimEncoder.encPLMN(spoofedSimInfo.mcc + spoofedSimInfo.mnc)
                val plmnwactHex = SimEncoder.encPLMNwAcT("$plmn:4000,$plmn:8000,$plmn:0080")
                val fplmn =
                    SimEncoder.encPLMN("46000，46001,46002,46006,46007,46011,46012,46015,46020")
                val telephonyManager =
                    context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
                val apduChannel = ApduChannel(telephonyManager, SIMView.AID_CUSTOM)
                apduChannel.select(SIMView.FID_MF)
                apduChannel.select(SIMView.FID_EF_ICCID)
                apduChannel.writeBinary(SimEncoder.encICCID(spoofedSimInfo.iccid))

                apduChannel.select(SIMView.FID_MF)
                apduChannel.select(SIMView.FID_DF_TELECOM)
                apduChannel.select(SIMView.FID_EF_MSISDN)
                apduChannel.writeRecord(
                    1, SimEncoder.encMSISDN(spoofedSimInfo.number)
                        .padStart(56, 'F')
                )

                apduChannel.select(SIMView.FID_MF)
                apduChannel.select(SIMView.FID_DF_GSM)
                apduChannel.select(SIMView.FID_EF_IMSI)
                apduChannel.writeBinary(SimEncoder.encIMSI(spoofedSimInfo.imsi))

                apduChannel.select(SIMView.FID_EF_PLMNSEL)
                apduChannel.writeBinary(plmnHex.padEnd(120, 'f'))

                apduChannel.select(SIMView.FID_EF_EHPLMN)
                apduChannel.writeBinary(plmnHex.padEnd(24, 'f'))

                apduChannel.select(SIMView.FID_EF_PLMNWACT)
                apduChannel.writeBinary(plmnwactHex.padEnd(240, 'f'))

                apduChannel.select(SIMView.FID_EF_OPLMNWACT)
                apduChannel.writeBinary(plmnwactHex.padEnd(120, 'f'))

                apduChannel.select(SIMView.FID_EF_HPLMNWACT)
                apduChannel.writeBinary(plmnwactHex.padEnd(40, 'f'))

                apduChannel.select(SIMView.FID_EF_FPLMN)
                apduChannel.writeBinary(fplmn.padEnd(60, 'f'))

                apduChannel.select(SIMView.FID_EF_SPN)
                apduChannel.writeBinary("01542d4d6f62696c65ffffffffffffffff") //T-Mobile

                if (plmn.isNotEmpty()) {
                    if (plmn.length == 5) {
                        apduChannel.select(SIMView.FID_EF_AD)
                        apduChannel.writeBinary("00000102")
                    } else if (plmn.length == 6) {
                        apduChannel.select(SIMView.FID_EF_AD)
                        apduChannel.writeBinary("00000103")
                    }
                }
                apduChannel.close()

                val apduChannel1 = ApduChannel(telephonyManager, SIMView.AID_USIM)
                apduChannel1.writeMSISDN_USIM(spoofedSimInfo.number)
                apduChannel.close()

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    telephonyManager.rebootModem()
                }

                val subscriptionManager =
                    context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager

                run rebootModem@{
                    repeat(20) {
                        runCatching {
                            val activeSubscriptionInfoList =
                                subscriptionManager.activeSubscriptionInfoList
                            activeSubscriptionInfoList?.forEach {
                                Log.i(TAG, "iccid:${it.iccId} target:${spoofedSimInfo.iccid}")
                                if (it.iccId == spoofedSimInfo.iccid) {
                                    Log.i(TAG, "rebootModem: success")
                                    return@rebootModem
                                }
                            }
                        }.onFailure {
                            Log.e(TAG, "Error reading ICCID: ${it.message}", it)
                        }
                        delay(2000)
                    }
                }
            }

            if (suspend == true) {
                shellRun(
                    PACKAGE_GMS.kill(),
                    "sleep 3",
                    PACKAGE_MESSAGING.kill(),
                    "sleep 3"
                )
            }

//            val base64 = Base64.encode(jsonObject.toString().toByteArray(), Base64.DEFAULT)
//                .toString(Charsets.UTF_8)
//            shellRun("echo '$base64' > /sdcard/Android/data/com.android.phone/files/config/config.json")

        } catch (e: Exception) {
            Log.e(TAG, "Error updateSpoofedSimInfo: ${e.message}", e)
        }
    }

    suspend fun updateAvailable(available: Boolean) {
        context.simInfoDataStore.edit {
            it[PreferencesKeys.AVAILABLE] = available
        }
    }

    suspend fun mock() {
        if (isOldVersion(context)) {
            val content = getContext().assets.open("us_numbers.txt").bufferedReader().use {
                it.readText()
            }
            // get random number
            content.split("\n")
                .filter { it.isNotBlank() }
                .shuffled()
                .firstOrNull()
                ?.let {
                    updateSpoofedSimInfo(
                        SpoofedSimInfo(
                            numberId = 0,
                            number = it,
                            mcc = "310",
                            mnc = "150",
                            iccid = genICCID("150", "1"),
                            imsi = genIMSI("310150"),
                            imei = genIMEI(),
                            country = "us",
                            areaCode = "1",
                            available = false,
                            carrierId = "1779",
                            carrierName = "Cricket Wireless",
                            serialNo = genSerialNo(),
                            mac = genMacAddress(),
                            bssid = genMacAddress()
                        ),
                        suspend = false
                    )
                }
        } else {
            val content = getContext().assets.open("us_numbers.txt")
                .bufferedReader().use { it.readText() }
            // get random number
            content.split("\n")
                .filter { it.isNotBlank() }
                .shuffled()
                .firstOrNull()
                ?.let {
                    updateSpoofedSimInfo(
                        SpoofedSimInfo(
                            numberId = 0,
                            number = it,
                            mcc = "310",
                            mnc = "240",
                            iccid = genICCID("240", "1"),
                            imsi = genIMSI("310240"),
                            imei = genIMEI(),
                            country = "us",
                            areaCode = "1",
                            available = false,
                            carrierId = "1",
                            carrierName = "T-Mobile",
                            serialNo = genSerialNo(),
                            mac = genMacAddress(),
                            bssid = genMacAddress()
                        )
                    )
                }
        }
    }

}