package com.ledgrab.android

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.media.projection.MediaProjectionManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import android.app.Activity
import androidx.core.content.ContextCompat
import com.google.zxing.BarcodeFormat
import com.google.zxing.qrcode.QRCodeWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Main (and only) Activity for the Android TV app.
 *
 * Two-state UI: stopped (Start button) and running (URL + QR + Stop).
 * Navigable with D-pad / USB controller.
 */
class MainActivity : Activity() {

    // Activity-scoped coroutine scope. We don't depend on AppCompat /
    // androidx.lifecycle's lifecycleScope here because the TV launcher
    // theme inherits from Leanback (non-AppCompat).
    private val uiScope: CoroutineScope = MainScope()

    companion object {
        private const val TAG = "MainActivity"
        private const val SERVER_PORT = 8080
        private const val REQUEST_MEDIA_PROJECTION = 1001
        private const val REQUEST_POST_NOTIFICATIONS = 1002
    }

    private lateinit var stoppedPanel: View
    private lateinit var runningPanel: View
    private lateinit var statusText: TextView
    private lateinit var urlText: TextView
    private lateinit var qrImage: ImageView
    private lateinit var toggleButton: Button
    private lateinit var stopButtonRunning: Button
    private lateinit var versionText: TextView
    private lateinit var autostartCheck: CheckBox
    private lateinit var autostartPrefs: AutostartPrefs

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Surface fatal Python init errors instead of crashing.
        val initError = (application as? LedGrabApp)?.initError
        if (initError != null) {
            showFatalErrorScreen(initError)
            return
        }

        setContentView(R.layout.activity_main)

        stoppedPanel = findViewById(R.id.stopped_panel)
        runningPanel = findViewById(R.id.running_panel)
        statusText = findViewById(R.id.status_text)
        urlText = findViewById(R.id.url_text)
        qrImage = findViewById(R.id.qr_image)
        toggleButton = findViewById(R.id.toggle_button)
        stopButtonRunning = findViewById(R.id.stop_button_running)
        versionText = findViewById(R.id.version_text)
        autostartCheck = findViewById(R.id.autostart_check)

        val versionName = packageManager
            .getPackageInfo(packageName, 0).versionName
        versionText.text = getString(R.string.version_prefix, versionName ?: "?")

        autostartPrefs = AutostartPrefs(this)
        autostartCheck.isChecked = autostartPrefs.isEnabled
        // Autostart only takes effect on rooted devices — grey it out
        // on unrooted hardware so users don't expect magic. Cheap probe
        // (file-existence only, no process spawn).
        if (!Root.looksRooted()) {
            autostartCheck.isEnabled = false
            autostartCheck.text = getString(R.string.autostart_unavailable)
        }
        autostartCheck.setOnCheckedChangeListener { _, isChecked ->
            autostartPrefs.isEnabled = isChecked
            if (isChecked) ensureIgnoringBatteryOptimizations()
        }

        toggleButton.setOnClickListener { startCapture() }
        stopButtonRunning.setOnClickListener { stopCaptureService() }

        updateUI()
    }

    /**
     * Decide whether to go through the MediaProjection consent flow or
     * jump straight into root capture. Root check is fast but may block
     * briefly the first time Magisk shows its grant dialog — running it
     * on the UI thread is acceptable because we're responding to a
     * button press and we want to block until the user answers.
     */
    override fun onDestroy() {
        uiScope.cancel()
        super.onDestroy()
    }

    private fun startCapture() {
        // `su -c id` can block for seconds while Magisk shows its grant
        // dialog; running it on the Main thread caused ANRs.
        toggleButton.isEnabled = false
        statusText.text = "Checking root access…"
        uiScope.launch(Dispatchers.IO) {
            val rooted = Root.requestGrant()
            withContext(Dispatchers.Main) {
                toggleButton.isEnabled = true
                statusText.text = ""
                if (rooted) {
                    Log.i(TAG, "Root available — skipping MediaProjection consent")
                    startRootCaptureService()
                } else {
                    requestMediaProjection()
                }
            }
        }
    }

    private fun requestMediaProjection() {
        val manager = getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
        @Suppress("DEPRECATION")
        startActivityForResult(manager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION)
    }

    private fun startRootCaptureService() {
        ensureNotificationPermission()
        ContextCompat.startForegroundService(this, CaptureService.createRootIntent(this))
        updateUI()
    }

    @Deprecated("Using deprecated API for plain Activity compatibility")
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_MEDIA_PROJECTION) {
            if (resultCode == RESULT_OK && data != null) {
                startCaptureService(resultCode, data)
            } else {
                statusText.text = "Permission denied — screen capture requires authorization"
                Log.w(TAG, "MediaProjection permission denied")
            }
        }
    }

    private fun startCaptureService(resultCode: Int, resultData: Intent) {
        ensureNotificationPermission()
        val intent = CaptureService.createIntent(this, resultCode, resultData)
        ContextCompat.startForegroundService(this, intent)
        updateUI()
    }

    private fun stopCaptureService() {
        stopService(Intent(this, CaptureService::class.java))
        updateUI()
    }

    private fun updateUI() {
        if (CaptureService.isRunning) {
            val localIp = NetworkUtils.getLocalIpAddress(this) ?: "unknown"
            val url = "http://$localIp:$SERVER_PORT"

            urlText.text = url
            qrImage.setImageBitmap(null)
            // Build the bitmap pixels off the Main thread — encode + 313k
            // setPixel calls were noticeably janky on slow TV boxes.
            uiScope.launch(Dispatchers.Default) {
                val bitmap = generateQrCode(url)
                withContext(Dispatchers.Main) {
                    if (CaptureService.isRunning && urlText.text == url) {
                        qrImage.setImageBitmap(bitmap)
                    }
                }
            }

            stoppedPanel.visibility = View.GONE
            versionText.visibility = View.GONE
            runningPanel.visibility = View.VISIBLE
            stopButtonRunning.requestFocus()
        } else {
            urlText.text = ""
            qrImage.setImageBitmap(null)

            runningPanel.visibility = View.GONE
            stoppedPanel.visibility = View.VISIBLE
            versionText.visibility = View.VISIBLE
            toggleButton.requestFocus()
        }
    }

    private fun generateQrCode(text: String): Bitmap {
        val size = 560
        val bitMatrix = QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, size, size)
        val pixels = IntArray(size * size)
        for (y in 0 until size) {
            val rowOffset = y * size
            for (x in 0 until size) {
                pixels[rowOffset + x] =
                    if (bitMatrix[x, y]) 0xFF000000.toInt() else 0xFFFFFFFF.toInt()
            }
        }
        val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565)
        bitmap.setPixels(pixels, 0, size, 0, 0, size, size)
        return bitmap
    }

    /**
     * Minimal failure UI shown when Python.start() (Chaquopy) blew up.
     * Rendered programmatically so we don't depend on the regular layout
     * (which itself may reference resources affected by the failure).
     */
    private fun showFatalErrorScreen(error: Throwable) {
        Log.e(TAG, "Fatal init error — showing error screen", error)
        val stackText = android.util.Log.getStackTraceString(error)
        val container = android.widget.LinearLayout(this).apply {
            orientation = android.widget.LinearLayout.VERTICAL
            setPadding(48, 48, 48, 48)
        }
        val title = TextView(this).apply {
            text = "LedGrab failed to start"
            textSize = 22f
        }
        val body = TextView(this).apply {
            text = "Python runtime initialization failed:\n\n$stackText"
            textSize = 12f
            setTextIsSelectable(true)
        }
        val copyBtn = Button(this).apply {
            text = "Copy log"
            setOnClickListener {
                val cm = getSystemService(CLIPBOARD_SERVICE)
                    as android.content.ClipboardManager
                cm.setPrimaryClip(
                    android.content.ClipData.newPlainText("LedGrab error", stackText)
                )
            }
        }
        val scroll = android.widget.ScrollView(this).apply { addView(body) }
        container.addView(title)
        container.addView(copyBtn)
        container.addView(scroll)
        setContentView(container)
    }

    /**
     * Prompt the user to exempt LedGrab from battery optimization.  On
     * TV boxes this is usually a no-op, but on phones Doze/App Standby
     * will kill the foreground service after a few hours of sleep.  We
     * only ask when autostart is turned on.  No-op on pre-M or when
     * already exempt.
     *
     * Play Store flags REQUEST_IGNORE_BATTERY_OPTIMIZATIONS by default
     * — LedGrab's ambient-capture use case falls under the documented
     * acceptable-use exceptions (a foreground service that must not be
     * killed to fulfill its primary function).
     */
    @SuppressLint("BatteryLife")
    private fun ensureIgnoringBatteryOptimizations() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
        val pm = getSystemService(POWER_SERVICE) as PowerManager
        if (pm.isIgnoringBatteryOptimizations(packageName)) return
        try {
            val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
                data = Uri.parse("package:$packageName")
            }
            startActivity(intent)
        } catch (e: Exception) {
            // Some TV-box OEM builds strip this intent. Fall back to the
            // generic settings screen so the user can find it manually.
            Log.w(TAG, "Direct exemption intent unavailable: ${e.message}")
            runCatching {
                startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
            }
        }
    }

    /**
     * Request POST_NOTIFICATIONS permission on Android 13+ so the
     * foreground service notification is visible.  On older API levels
     * this is a no-op.
     */
    private fun ensureNotificationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
                != PackageManager.PERMISSION_GRANTED
            ) {
                @Suppress("DEPRECATION")
                requestPermissions(
                    arrayOf(Manifest.permission.POST_NOTIFICATIONS),
                    REQUEST_POST_NOTIFICATIONS,
                )
            }
        }
    }
}
