package com.ledgrab.android

import android.content.Context
import android.util.Log
import com.chaquo.python.PyObject
import com.chaquo.python.Python

/**
 * Bridge between Kotlin and the LedGrab Python server.
 *
 * All Python calls go through Chaquopy's `Python.getInstance()`.
 * Frame data crosses the JNI boundary as a `ByteArray` (reused across
 * frames — see ScreenCapture / RootScreenrecord for buffer pools).
 */
class PythonBridge(private val context: Context) {

    companion object {
        private const val TAG = "PythonBridge"
    }

    private var serverThread: Thread? = null
    @Volatile private var running = false

    // Cached PyObject handles for the per-frame fast path. Looking these
    // up via Python.getInstance().getModule(...) every frame was a real
    // measurable cost (~1ms/frame on TV boxes). Cached once at configure
    // time and read on the capture thread — @Volatile is enough for the
    // single-writer/single-reader pattern we have here.
    @Volatile private var mediaProjectionEngine: PyObject? = null
    @Volatile private var rootEngine: PyObject? = null

    /**
     * Configure the MediaProjection engine with screen dimensions.
     * Must be called before [startServer].
     */
    fun configureCapture(width: Int, height: Int) {
        val py = Python.getInstance()
        val engine = py.getModule("ledgrab.core.capture_engines.mediaprojection_engine")
        engine.callAttr("configure", width, height)
        mediaProjectionEngine = engine
        Log.i(TAG, "MediaProjection engine configured: ${width}x${height}")
    }

    /**
     * Configure the root screenrecord engine with screen dimensions.
     * Used instead of [configureCapture] when root is available.
     */
    fun configureRootCapture(width: Int, height: Int) {
        val py = Python.getInstance()
        val engine = py.getModule("ledgrab.core.capture_engines.root_screenrecord_engine")
        engine.callAttr("configure", width, height)
        rootEngine = engine
        Log.i(TAG, "Root screenrecord engine configured: ${width}x${height}")
    }

    /**
     * Start the LedGrab FastAPI server on a background thread.
     *
     * Passes [apiKey] through so the Python server's auth gate accepts
     * Bearer-authenticated LAN requests; null disables auth (loopback
     * only — see [ApiKeyManager]).
     *
     * This blocks until [stopServer] is called, so it runs in its own
     * thread.
     */
    fun startServer(port: Int = 8080, apiKey: String? = null) {
        if (running) {
            Log.w(TAG, "Server already running")
            return
        }

        running = true
        val dataDir = context.filesDir.absolutePath

        serverThread = Thread({
            try {
                Log.i(TAG, "Starting Python server (dataDir=$dataDir, port=$port)")
                val py = Python.getInstance()
                val entry = py.getModule("ledgrab.android_entry")
                if (apiKey != null) {
                    entry.callAttr("start_server", dataDir, port, apiKey)
                } else {
                    entry.callAttr("start_server", dataDir, port)
                }
            } catch (e: Exception) {
                Log.e(TAG, "Python server error", e)
            } finally {
                running = false
            }
        }, "ledgrab-python-server")
        serverThread?.start()
    }

    /**
     * Signal the Python server to shut down gracefully.
     */
    fun stopServer() {
        if (!running) return

        try {
            val py = Python.getInstance()
            val entry = py.getModule("ledgrab.android_entry")
            entry.callAttr("stop_server")
        } catch (e: Exception) {
            Log.e(TAG, "Error stopping server", e)
        }

        serverThread?.join(10_000)
        serverThread = null
        running = false
        Log.i(TAG, "Server stopped")
    }

    /**
     * Push a captured RGBA frame to the Python MediaProjection engine.
     *
     * Called from [ScreenCapture] on the capture thread.  The byte array
     * crosses the JNI boundary — keep frames small (downscale to 480p
     * before calling) and pass reusable buffers (see ScreenCapture's
     * buffer pool).
     */
    fun pushFrame(rgbaBytes: ByteArray, width: Int, height: Int) {
        if (!running) return
        val engine = mediaProjectionEngine ?: return

        try {
            engine.callAttr("push_frame", rgbaBytes, width, height)
        } catch (e: Exception) {
            Log.w(TAG, "Failed to push frame: ${e.message}")
        }
    }

    /**
     * Push a frame produced by the root `screenrecord` pipeline.
     *
     * Routed to a separate Python module so the two capture paths stay
     * independently introspectable (engine priority, availability flags,
     * dashboard diagnostics).
     */
    fun pushRootFrame(rgbaBytes: ByteArray, width: Int, height: Int) {
        if (!running) return
        val engine = rootEngine ?: return

        try {
            engine.callAttr("push_frame", rgbaBytes, width, height)
        } catch (e: Exception) {
            Log.w(TAG, "Failed to push root frame: ${e.message}")
        }
    }

    val isRunning: Boolean get() = running
}
