package com.example.modifier.utils

import android.util.Log
import com.example.modifier.model.AdbResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class ADB {
    companion object {
        const val TAG = "ADB"
        val adbPath by lazy { "${getContext().applicationInfo.nativeLibraryDir}/libadb.so" }
        private val homeDir by lazy { getContext().filesDir }
        private val cacheDir by lazy { getContext().cacheDir }

        suspend fun connect(): Boolean {
            return run ls@{
                repeat(1000) {
                    adb("connect", "localhost:5555")
                    delay(2000)
                    adb("devices")
                    if (adb("shell", "echo", "hello").exitCode == 0) {
                        return@ls true
                    }
                    delay(2000)
                }
                return@ls false
            }
        }

        /**
         * Send a raw ADB command
         */
        suspend fun adb(vararg command: String): AdbResult {
            runCatching {
                val fullCommand = command.toMutableList().also {
                    it.add(0, adbPath)
                    if (command.firstOrNull() == "shell") {
                        it.add(1, "-s")
                        it.add(2, "localhost:5555")
                    }
                }
                Log.i(TAG, "ADB command: ${fullCommand.joinToString(" ")}".replace(adbPath, "adb"))
                var output = ""
                var error = ""
                val process = withContext(Dispatchers.IO) {
                    createProcess(*fullCommand.toTypedArray())
                }
                coroutineScope {
                    launch {
                        process.inputStream.bufferedReader().useLines { line ->
                            line.forEach {
                                output += it + "\n"
                                Log.i(TAG, "ADB out: $it")
                            }
                        }
                    }
                    launch {
                        process.errorStream.bufferedReader().useLines { line ->
                            line.forEach {
                                error += it + "\n"
                                Log.e(TAG, "ADB err: $it")
                            }
                        }
                    }
                }
                withContext(Dispatchers.IO) {
                    process.waitFor()
                }
                return AdbResult(exitCode = process.exitValue())
            }.onFailure {
                Log.wtf(TAG, "Error running ADB command: ${it.message}", it)
            }
            return AdbResult()
        }

        /**
         * Send a raw ADB command
         */
        suspend fun adbShell(vararg commands: String): AdbResult {
            Log.i(TAG, "ADB command: ${commands.joinToString("\n")}")
            runCatching {
                var output = ""
                var error = ""
                val process = withContext(Dispatchers.IO) {
                    createProcess(adbPath, "-s", "localhost:5555", "shell")
                }
                process.outputStream.bufferedWriter().use { writer ->
                    commands.forEach { command ->
                        writer.write(command)
                        writer.newLine()
                        writer.flush()
                    }
                }
                coroutineScope {
                    launch {
                        process.inputStream.bufferedReader().useLines { line ->
                            line.forEach {
                                output += it + "\n"
                                Log.i(TAG, "ADB out: $it")
                            }
                        }
                    }
                    launch {
                        process.errorStream.bufferedReader().useLines { line ->
                            line.forEach {
                                error += it + "\n"
                                Log.e(TAG, "ADB err: $it")
                            }
                        }
                    }
                }
                withContext(Dispatchers.IO) {
                    process.waitFor()
                }
                Log.i(TAG, "ADB exit code: ${process.exitValue()}")
                return AdbResult(output = output, error = error, exitCode = process.exitValue())
            }.onFailure {
                Log.wtf(TAG, "Error running ADB command: ${it.message}", it)
            }
            return AdbResult()
        }

        fun createProcess(vararg command: String): Process {
            return ProcessBuilder(*command)
                .directory(homeDir)
                .apply {
//                    redirectErrorStream(true)
                    environment().apply {
                        put("HOME", homeDir.path)
                        put("TMPDIR", cacheDir.path)
                    }
                }.start()
        }
    }
}