package com.nothing.camera2magic

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.companion.AssociationInfo
import android.companion.AssociationRequest
import android.companion.CompanionDeviceManager
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.os.Binder
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.view.Surface
import androidx.annotation.RequiresApi
import com.nothing.camera2magic.hook.VideoPusher
import com.nothing.camera2magic.R
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Proxy
import java.util.concurrent.Executor
import java.util.concurrent.Executors

/**
 * [VirtualCameraService] wraps Android VDM/VirtualCamera @SystemApi behind reflection.
 *
 * On this device (Pixel 9a, Android 16) the VDM classes live in
 * `android.companion.virtual.*` (not `android.companion.*`).
 * We obtain the service via the public [getSystemService] API, then
 * derive the correct package prefix from the returned object's class at
 * runtime so the class names are always correct regardless of device build.
 *
 * CompanionDeviceManager, AssociationRequest, AssociationInfo are public API.
 */
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class VirtualCameraService : Service() {

    companion object {
        const val TAG = "VirtualCameraService"
        const val CHANNEL_ID = "virtual_camera_channel"
        const val NOTIFICATION_ID = 1
    }

    private var virtualDevice: Any? = null
    private var videoPusherActive = false
    private var callbackExecutor: Executor? = null
    private var vdmPkg: String = "android.companion.virtual" // default, overridden at runtime

    private val binder = LocalBinder()

    inner class LocalBinder : Binder() {
        fun getService(): VirtualCameraService = this@VirtualCameraService
    }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
        Log.i(TAG, "Service created")
    }

    override fun onBind(intent: Intent?): IBinder = binder

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForeground(NOTIFICATION_ID, buildNotification("Virtual camera service running"))
        Log.i(TAG, "onStartCommand")
        return START_STICKY
    }

    override fun onDestroy() {
        stopVirtualCamera()
        super.onDestroy()
        Log.i(TAG, "Service destroyed")
    }

    private fun vdmClass(shortName: String): Class<*> =
        Class.forName("$vdmPkg.$shortName")

    fun startVirtualCamera() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            Log.e(TAG, "VirtualDeviceManager requires API 34+")
            updateNotification("Error: API 34+ required")
            return
        }

        try {
            val vdm = getSystemService("virtualdevice") ?: run {
                Log.e(TAG, "VirtualDeviceManager not available")
                updateNotification("Error: VirtualDeviceManager not available")
                return
            }
            Log.i(TAG, "VirtualDeviceManager obtained: $vdm")

            // Extract the actual VDM package (e.g. android.companion.virtual)
            // from the runtime object so class names are correct on any build.
            val vdmClass = vdm.javaClass
            val fullName = vdmClass.name
            vdmPkg = fullName.substringBeforeLast(".")

            val params = buildVirtualDeviceParams()
            Log.i(TAG, "VirtualDeviceParams created")

            startCdmAssociation(vdm, vdmClass, params)

        } catch (e: Exception) {
            Log.e(TAG, "Failed to start virtual camera", e)
            updateNotification("Error: ${e.message}")
        }
    }

    fun stopVirtualCamera() {
        try {
            if (videoPusherActive) {
                VideoPusher.stop()
                videoPusherActive = false
            }
            virtualDevice?.let { device ->
                device.javaClass.getMethod("close").invoke(device)
                virtualDevice = null
                Log.i(TAG, "Virtual camera stopped")
                updateNotification("Virtual camera stopped")
            }
            callbackExecutor?.let { (it as? java.util.concurrent.ExecutorService)?.shutdown() }
            callbackExecutor = null
        } catch (e: Exception) {
            Log.e(TAG, "Error stopping virtual camera", e)
        }
    }

    private fun buildVirtualDeviceParams(): Any {
        val paramsClass = vdmClass("VirtualDeviceParams")
        val builderClass = vdmClass("VirtualDeviceParams\$Builder")
        val builder = builderClass.getDeclaredConstructor().newInstance()

        builderClass.getMethod("setName", String::class.java)
            .invoke(builder, "CamSwapper Virtual Camera")

        val policyTypeCamera = paramsClass.getDeclaredField("POLICY_TYPE_CAMERA").get(null) as Int
        val devicePolicyCustom = paramsClass.getDeclaredField("DEVICE_POLICY_CUSTOM").get(null) as Int
        builderClass.getMethod("setDevicePolicy", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
            .invoke(builder, policyTypeCamera, devicePolicyCustom)

        return builderClass.getMethod("build").invoke(builder)
    }

    private fun startCdmAssociation(vdm: Any, vdmClass: Class<*>, params: Any) {
        val cdm = getSystemService(CompanionDeviceManager::class.java)
        if (cdm == null) {
            Log.w(TAG, "CompanionDeviceManager not available")
            updateNotification("Error: CDM not available")
            return
        }

        // Use setDeviceProfile() — public API since API 31 — instead of
        // DeviceFilter.Builder (which doesn't exist on all SDK builds).
        // DEVICE_PROFILE_APP_STREAMING grants us a companion association for
        // virtual display / app streaming use-cases (i.e. virtual camera).
        val request = try {
            AssociationRequest.Builder()
                .setSingleDevice(true)
                .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_APP_STREAMING)
                .setSelfManaged(true)
                .setDisplayName("CamSwapper Virtual Camera")
                .build()
        } catch (e: Exception) {
            Log.e(TAG, "Failed to build AssociationRequest", e)
            updateNotification("Error: Association request failed")
            return
        }
        val handler = Handler(Looper.getMainLooper())

        val callback = object : CompanionDeviceManager.Callback() {
            override fun onAssociationPending(intentSender: IntentSender) {
                Log.i(TAG, "CDM association pending — requesting user consent")
                try {
                    startIntentSender(intentSender, null, 0, 0, 0)
                } catch (e: Exception) {
                    // CompanionAssociationActivity may not exist on some builds.
                    Log.e(TAG, "Failed to show consent dialog", e)
                    // Try creating virtual device without CDM association
                    Log.i(TAG, "Trying direct VDM association bypass...")
                    tryDirectVdm(vdm, vdmClass, params)
                }
            }

            override fun onAssociationCreated(associationInfo: AssociationInfo) {
                Log.i(TAG, "CDM association created: id=${associationInfo.id}")
                val method = vdmClass.getMethod(
                    "createVirtualDevice",
                    Int::class.javaPrimitiveType,
                    vdmClass("VirtualDeviceParams")
                )
                @Suppress("UNCHECKED_CAST")
                val device = method.invoke(vdm, associationInfo.id, params) as? Any
                if (device == null) {
                    Log.e(TAG, "createVirtualDevice returned null")
                    // Fallback: try without CDM
                    tryDirectVdm(vdm, vdmClass, params)
                    return
                }
                Log.i(TAG, "VirtualDevice created: $device")
                virtualDevice = device
                createVirtualCamera(device)
            }

            override fun onFailure(error: CharSequence?) {
                Log.e(TAG, "CDM association failed: $error")
                // Fallback: try without CDM
                tryDirectVdm(vdm, vdmClass, params)
            }
        }

        try {
            cdm.associate(request, callback, handler)
            Log.i(TAG, "CDM association requested — waiting for user consent")
        } catch (e: Exception) {
            Log.e(TAG, "cdm.associate threw, trying direct VDM", e)
            tryDirectVdm(vdm, vdmClass, params)
        }
    }

    /**
     * Attempt to create a VirtualDevice without CDM association by probing
     * the [VirtualDeviceManager] methods at runtime. On some builds the
     * device may accept a request without a companion association (e.g.
     * via reflection on internal methods or a privileged-only overload).
     */
    private fun tryDirectVdm(vdm: Any, vdmClass: Class<*>, params: Any) {
        try {
            // Log available methods for debugging
            Log.i(TAG, "=== VirtualDeviceManager methods ===")
            for (m in vdmClass.methods.sortedBy { it.name }) {
                Log.i(TAG, "  ${m.name}(${m.parameterTypes.joinToString { it.simpleName }})")
            }

            // Try createVirtualDevice(VirtualDeviceParams) — no association needed
            val directMethod = vdmClass.methods.firstOrNull { m ->
                m.name == "createVirtualDevice" &&
                    m.parameterTypes.size == 1
            }
            if (directMethod != null) {
                Log.i(TAG, "Found direct createVirtualDevice, trying...")
                val device = directMethod.invoke(vdm, params) as? Any
                if (device != null) {
                    Log.i(TAG, "VirtualDevice created via direct API: $device")
                    virtualDevice = device
                    createVirtualCamera(device)
                    return
                }
                Log.w(TAG, "Direct createVirtualDevice returned null")
            }

            // Try createVirtualDevice(0, params) with dummy association ID
            val intMethod = vdmClass.getMethod(
                "createVirtualDevice",
                Int::class.javaPrimitiveType,
                vdmClass("VirtualDeviceParams")
            )
            Log.i(TAG, "Trying createVirtualDevice(0, params)...")
            val device = intMethod.invoke(vdm, 0, params) as? Any
            if (device != null) {
                Log.i(TAG, "VirtualDevice created with id=0: $device")
                virtualDevice = device
                createVirtualCamera(device)
                return
            }
            Log.w(TAG, "createVirtualDevice(0, params) returned null")
            updateNotification("Error: No VDM method worked")
        } catch (e: Exception) {
            Log.e(TAG, "All VDM methods failed", e)
            updateNotification("Error: VDM unavailable")
        }
    }

    /**
     * Helper to load a class from a sub-package under [vdmPkg].
     * On Android 16 (API 36) virtual camera classes moved from
     * `android.companion.virtual.*` to `android.companion.virtual.camera.*`.
     */
    private fun vdmSubClass(subPkg: String, shortName: String): Class<*> =
        Class.forName("$vdmPkg.$subPkg.$shortName")

    private fun createVirtualCamera(device: Any) {
        try {
            val cameraPkg = "camera"

            val callbackClass = vdmSubClass(cameraPkg, "VirtualCameraCallback")
            val executor = Executors.newSingleThreadExecutor()
            callbackExecutor = executor

            val callback = Proxy.newProxyInstance(
                callbackClass.classLoader,
                arrayOf(callbackClass),
                InvocationHandler { _, method, args ->
                    when (method.name) {
                        "onStreamConfigured" -> {
                            // Android 16 passes 5 args: streamId, Surface, width, height, format
                            val streamId = args[0] as Int
                            val surface = args[1] as Surface
                            val width = args[2] as Int
                            val height = args[3] as Int
                            Log.i(
                                TAG,
                                "onStreamConfigured: streamId=$streamId, ${width}x${height}"
                            )
                            onStreamConfigured(surface, width, height)
                        }
                        "onStreamClosed" -> {
                            val streamId = args[0] as Int
                            Log.i(TAG, "onStreamClosed: streamId=$streamId")
                            onStreamClosed()
                        }
                        "onProcessCaptureRequest" -> {
                            // Android 16 callback — no-op for now
                            Log.d(TAG, "onProcessCaptureRequest: streamId=${args[0]}")
                        }
                        else -> {
                            Log.w(TAG, "Unknown VirtualCameraCallback: ${method.name}")
                            null
                        }
                    }
                }
            )

            val configBuilderClass = vdmSubClass(cameraPkg, "VirtualCameraConfig\$Builder")
            val configClass = vdmSubClass(cameraPkg, "VirtualCameraConfig")

            // Probe available constructors: try single-arg (String) first, fall back to (String, Set)
            val configBuilder = try {
                configBuilderClass.getDeclaredConstructor(String::class.java)
                    .newInstance("CamSwapper")
            } catch (_: NoSuchMethodException) {
                configBuilderClass.getDeclaredConstructor(
                    String::class.java,
                    Set::class.java
                ).newInstance("CamSwapper", emptySet<Any>())
            }

            configBuilderClass.getMethod(
                "addStreamConfig",
                Int::class.javaPrimitiveType,
                Int::class.javaPrimitiveType,
                Int::class.javaPrimitiveType,
                Int::class.javaPrimitiveType
            ).invoke(configBuilder, 1920, 1080, android.graphics.ImageFormat.YUV_420_888, 30)

            // CameraCharacteristics.LENS_FACING_FRONT = 0 (stable since API 21)
            // Use literal value to avoid reflection issues on Android 16
            configBuilderClass.getMethod("setLensFacing", Int::class.javaPrimitiveType)
                .invoke(configBuilder, 0)

            configBuilderClass.getMethod(
                "setVirtualCameraCallback",
                Executor::class.java,
                callbackClass
            ).invoke(configBuilder, executor, callback)

            val config = configBuilderClass.getMethod("build").invoke(configBuilder)
            Log.i(TAG, "VirtualCameraConfig built")

            device.javaClass.getMethod("createVirtualCamera", configClass)
                .invoke(device, config)

            Log.i(TAG, "Virtual camera created and running")
            updateNotification("Virtual camera running")

        } catch (e: Exception) {
            Log.e(TAG, "Failed to create virtual camera", e)
            updateNotification("Error: ${e.message}")
        }
    }

    private fun onStreamConfigured(surface: Surface, width: Int, height: Int) {
        updateNotification("Stream active: ${width}x${height}")

        val prefs = getSharedPreferences("camera_magic_config", MODE_PRIVATE)
        val videoId = prefs.getLong("local_video_id", -1L)

        if (videoId > 0) {
            VideoPusher.start(listOf(surface), width, height, videoId)
            videoPusherActive = true
        } else {
            Log.w(TAG, "No video selected (videoId=$videoId)")
        }
    }

    private fun onStreamClosed() {
        if (videoPusherActive) {
            VideoPusher.stop()
            videoPusherActive = false
        }
    }

    private fun createNotificationChannel() {
        val channel = NotificationChannel(
            CHANNEL_ID,
            "Virtual Camera Service",
            NotificationManager.IMPORTANCE_LOW
        )
        val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        nm.createNotificationChannel(channel)
    }

    private fun buildNotification(text: String): Notification {
        return Notification.Builder(this, CHANNEL_ID)
            .setContentTitle("CamSwapper")
            .setContentText(text)
            .setSmallIcon(R.drawable.ic_notification)
            .build()
    }

    private fun updateNotification(text: String) {
        val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        nm.notify(NOTIFICATION_ID, buildNotification(text))
    }
}
