package com.example.modifier.service

import android.os.Build
import android.util.Log
import com.example.modifier.BuildConfig
import com.example.modifier.baseTag
import com.example.modifier.enums.ReqState
import com.example.modifier.model.CancelStoreNumberAction
import com.example.modifier.model.CheckPifAction
import com.example.modifier.model.CleanBackupAction
import com.example.modifier.model.InstallApkAction
import com.example.modifier.model.ResumeAction
import com.example.modifier.model.RunScriptAction
import com.example.modifier.model.RunScriptResult
import com.example.modifier.model.SocketCallback
import com.example.modifier.model.StoreNumberAction
import com.example.modifier.model.TaskAction
import com.example.modifier.model.TaskExecutionResult
import com.example.modifier.model.UpdateDeviceAction
import com.example.modifier.repo.AppPrefsRepo
import com.example.modifier.repo.AppStateRepo
import com.example.modifier.repo.SpoofedInfoRepo
import com.example.modifier.serializer.Json
import com.example.modifier.utils.checkPif
import com.example.modifier.utils.getIPAddress
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import org.json.JSONObject
import java.util.Timer
import kotlin.concurrent.schedule

class SocketClient(
    private val taskRunner: TaskRunner
) : Emitter.Listener {
    companion object {
        private const val TAG = "$baseTag/SocketClient"
    }

    private val mSocketOpts: IO.Options = IO.Options()
    private val mSocket: Socket

    init {
        val appPrefs = AppPrefsRepo.instance.appPrefs.value
        mSocketOpts.query =
            "model=${Build.MODEL}&name=${appPrefs.name}&id=${appPrefs.id}&version=${BuildConfig.VERSION_CODE}&ip=${
                getIPAddress().joinToString(",")
            }}"
        mSocketOpts.transports = arrayOf("websocket")

        mSocket = IO.socket(appPrefs.server, mSocketOpts)

        mSocket.on("message", this)
        mSocket.on(Socket.EVENT_CONNECT) { this.onConnected(it) }
        mSocket.on(Socket.EVENT_DISCONNECT) { this.onDisconnected(it) }
        mSocket.on(Socket.EVENT_CONNECT_ERROR) {
            it.forEach {
                Log.e(TAG, "Connection error", it as Throwable)
            }
        }
        try {
            mSocket.connect()
        } catch (e: Exception) {
            Log.e(TAG, "connect error", e)
        }

        val timer = Timer()
        timer.schedule(0, 3000) {
            reportDeviceStatues()
        }
    }

    fun disconnect() {
        mSocket.off()
        mSocket.disconnect()
    }

    private fun throwIfBusy() {
        if (AppStateRepo.instance.appState.value.busy) {
            throw Exception("Device is busy")
        }
    }

    override fun call(vararg args: Any?) {
        if (args.isEmpty()) {
            return
        }
        Log.i(TAG, "Received message: " + args[0])
        if (args[0] !is JSONObject) return
        val json = args[0] as JSONObject
        val action = json.optString("action")
        val id = json.optString("id")
        val jsonStr = json.toString()
        when (action) {
            TaskAction.NAME -> {
                try {
                    throwIfBusy()
                    val taskAction = Json.decodeFromString<TaskAction>(jsonStr)
                    CoroutineScope(Dispatchers.IO).launch {
                        taskRunner.runTask(taskAction, onSuccess = { res ->
                            socketCallback(taskAction.id, res)
                        }, onError = { error ->
                            socketCallback(
                                id = taskAction.id,
                                status = -1,
                                error = error.message
                            )
                        })
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "taskAction error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            InstallApkAction.NAME -> {
                try {
                    val installApkAction = Json.decodeFromString<InstallApkAction>(jsonStr)
                    if (true != installApkAction.data.interrupt) {
                        throwIfBusy()
                    }
                    CoroutineScope(Dispatchers.IO).launch {
                        taskRunner.installApk(installApkAction, onSuccess = {
                            socketCallback(
                                id = installApkAction.id,
                                status = 0,
                                data = "OK"
                            )
                        }, onError = { error ->
                            socketCallback(
                                id = installApkAction.id,
                                status = -1,
                                error = error.message
                            )
                        })
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "installApk error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            RunScriptAction.NAME -> {
                try {
                    val runScriptAction = Json.decodeFromString<RunScriptAction>(jsonStr)
                    CoroutineScope(Dispatchers.IO).launch {
                        taskRunner.runScript(runScriptAction, onSuccess = { out, err ->
                            socketCallback(runScriptAction.id, RunScriptResult(out, err))
                        }, onError = { error ->
                            socketCallback(
                                id = runScriptAction.id,
                                status = -1,
                                error = error.message
                            )
                        })
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "runScript error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            UpdateDeviceAction.NAME -> {
                try {
                    val updateDeviceAction = Json.decodeFromString<UpdateDeviceAction>(jsonStr)
                    CoroutineScope(Dispatchers.IO).launch {
                        taskRunner.updateDevice(updateDeviceAction, onSuccess = {
                            socketCallback(
                                id = updateDeviceAction.id,
                                status = 0,
                                data = "OK"
                            )
                        }, onError = { error ->
                            socketCallback(
                                id = updateDeviceAction.id,
                                status = -1,
                                error = error.message
                            )
                        })
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "updateDevice error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            StoreNumberAction.NAME -> {
                try {
                    throwIfBusy()
                    val storeNumberAction = Json.decodeFromString<StoreNumberAction>(jsonStr)
                    CoroutineScope(Dispatchers.IO).launch {
                        taskRunner.storeNumbers(storeNumberAction.data?.num)
                        socketCallback(id = storeNumberAction.id, status = 0, data = "OK")
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "storeNumber error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            CancelStoreNumberAction.NAME -> {
                try {
                    CoroutineScope(Dispatchers.IO).launch {
                        taskRunner.cancelStoreNumber()
                        socketCallback(id = id, status = 0, data = "OK")
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "cancelStoreNumber error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            ResumeAction.NAME -> {
                try {
                    throwIfBusy()
                    val resumeAction = Json.decodeFromString<ResumeAction>(jsonStr)
                    CoroutineScope(Dispatchers.IO).launch {
                        AppStateRepo.instance.updateRuntimeFlags(suspended = false)
                        AppStateRepo.instance.updateSend(true)
                        if (resumeAction.data?.request == true) {
                            taskRunner.requestNumberOnTask(
                                reset = resumeAction.data.reset ?: false
                            )
                        }
                    }
                    socketCallback(id = id, status = 0, data = "OK")
                } catch (e: Exception) {
                    Log.e(TAG, "resume error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            CheckPifAction.NAME -> {
                try {
                    val checkPifAction = Json.decodeFromString<CheckPifAction>(jsonStr)
                    CoroutineScope(Dispatchers.IO).launch {
                        checkPif()
                        socketCallback(
                            id = checkPifAction.id,
                            status = 0,
                            data = "OK"
                        )
                    }
                } catch (e: Exception) {
                    Log.e(TAG, "checkPif error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }

            CleanBackupAction.NAME -> {
                try {
                    val cleanBackupAction = Json.decodeFromString<CleanBackupAction>(jsonStr)
                    CoroutineScope(Dispatchers.IO).launch {
                        taskRunner.cleanBackup(
                            cleanBackupAction.data.start,
                            cleanBackupAction.data.end
                        )
                    }
                    socketCallback(id = id, status = 0, data = "OK")
                } catch (e: Exception) {
                    Log.e(TAG, "checkPif error", e)
                    socketCallback(id = id, status = -1, error = e.message)
                }
            }
        }
    }

    private fun onConnected(vararg args: Any) {
        Log.i(TAG, "Connected to server")
        CoroutineScope(Dispatchers.IO).launch {
            delay(500)
            reportDeviceStatues()
        }
    }

    private fun onDisconnected(vararg args: Any) {}

    fun reportDeviceStatues() {
        if (!mSocket.connected()) {
            return
        }
        val data = JSONObject()
        runCatching {
            data.put("action", "updateDevice")
            val dataObj = JSONObject()
            dataObj.put("canSend", AppStateRepo.instance.appState.value.send)
            dataObj.put("busy", AppStateRepo.instance.appState.value.busy)
            dataObj.put("currentCountry", SpoofedInfoRepo.instance.spoofedInfo.value.country)
            dataObj.put("ip", getIPAddress().joinToString(","))
            dataObj.put(
                "requesting",
                AppStateRepo.instance.appState.value.reqState == ReqState.REQUEST
            )
            dataObj.put("suspended", AppStateRepo.instance.appState.value.suspended)
            dataObj.put("storing", AppStateRepo.instance.appState.value.storing)
            data.put("data", dataObj)
            mSocket.emit("message", data)
        }.onFailure { e ->
            Log.e(TAG, "reportDeviceStatues error", e)
        }
    }

    private fun socketCallback(id: String, data: List<TaskExecutionResult>) {
        runCatching {
            mSocket.emit(
                "callback", JSONObject(
                    Json.encodeToString(
                        SocketCallback(
                            id = id,
                            status = 0,
                            data = data
                        )
                    )
                )
            )
        }.onFailure { e ->
            Log.e(TAG, "emitEvent error", e)
        }
    }

    private fun socketCallback(id: String, data: RunScriptResult) {
        runCatching {
            mSocket.emit(
                "callback", JSONObject(
                    Json.encodeToString(
                        SocketCallback(
                            id = id,
                            status = 0,
                            data = data
                        )
                    )
                )
            )
        }.onFailure { e ->
            Log.e(TAG, "emitEvent error", e)
        }
    }

    private fun socketCallback(
        id: String,
        status: Int,
        data: String? = null,
        error: String? = null
    ) {
        runCatching {
            mSocket.emit(
                "callback", JSONObject(
                    Json.encodeToString(
                        SocketCallback(
                            id = id,
                            status = status,
                            data = data,
                            error = error
                        )
                    )
                )
            )
        }.onFailure { e ->
            Log.e(TAG, "emitEvent error", e)
        }
    }
}