package com.nothing.camera2magic.hook

import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraCaptureSession.StateCallback
import android.os.Handler
import android.os.Looper
import com.nothing.camera2magic.GlobalState
import com.nothing.camera2magic.hook.NativeBridge
import com.nothing.camera2magic.utils.Dog
import com.nothing.camera2magic.hook.SourceManager
import com.nothing.camera2magic.hook.VideoPusher
import com.nothing.camera2magic.hook.BlackHoleMapper
import com.nothing.camera2magic.hook.CameraState
import io.github.libxposed.api.XposedModuleInterface.PackageReadyParam
import java.lang.ref.WeakReference
import java.util.Collections
import java.util.WeakHashMap
import kotlin.math.min
import android.annotation.SuppressLint
import android.view.Surface
import android.hardware.camera2.params.OutputConfiguration
import android.hardware.camera2.params.SessionConfiguration
import com.nothing.camera2magic.MagicHook

/**
 * Framework-level hooker for CameraDeviceImpl.
 * Handles system-wide camera session creation interception.
 */
object CameraDeviceImplHooker {
    private const val TAG = "[CAMDEV-IMPL-HK]"
    private lateinit var magic: MagicHook
    private val hookedClasses = Collections.synchronizedSet(
        Collections.newSetFromMap(WeakHashMap<Class<*>, Boolean>()))
    private var activeCameraRef: WeakReference<Any>? = null
    private val cameraState = WeakHashMap<CameraDevice, CameraState>()

    fun initHooks(module: MagicHook, param: PackageReadyParam) {
        Dog.i(TAG, "initHooks called", true)
        magic = module
        val classLoader = param.classLoader
        try {
            // Access the framework class to prepare for hooking
            Dog.i(TAG, "Loading CameraDeviceImpl class", true)
            val deviceImplClass = classLoader.loadClass("android.hardware.camera2.impl.CameraDeviceImpl")
            Dog.i(TAG, "CameraDeviceImpl loaded successfully", true)
            deviceImplClass.apply {
                Dog.i(TAG, "Hooking createCaptureSession variants", true)
                // Register each hook independently to avoid one failing blocking others
                try {
                    hookCreateCaptureSessionWithSurfaces()
                } catch (e: Exception) {
                    Dog.e(TAG, "Failed to register hook: createCaptureSessionWithSurfaces", e, true)
                }

                try {
                    hookCreateCaptureSessionByOutputConfigurations()
                } catch (e: Exception) {
                    Dog.e(TAG, "Failed to register hook: createCaptureSessionByOutputConfigurations", e, true)
                }

                try {
                    hookCreateCaptureSessionWithConfiguration()
                } catch (e: Exception) {
                    Dog.e(TAG, "Failed to register hook: createCaptureSessionWithConfiguration", e, true)
                }

                try {
                    hookClose()
                } catch (e: Exception) {
                    Dog.e(TAG, "Failed to register hook: close", e, true)
                }
                
                Dog.i(TAG, "Hook registration attempts finished", true)
            }
        } catch (e: Exception) {
            // Best-effort: do not crash module loading if framework class is unavailable
            Dog.e(TAG, "Failed to access CameraDeviceImpl", e, true)
        }
        
        // Android 14+ CameraDeviceSetup support
        try {
            val setupClass = classLoader.loadClass("android.hardware.camera2.impl.CameraDeviceSetupImpl")
            setupClass.apply {
                try {
                    hookCreateCaptureSessionWithConfiguration()
                } catch (e: Exception) {
                    Dog.e(TAG, "Failed to register hook: createCaptureSessionWithConfiguration in CameraDeviceSetupImpl", e, true)
                }
            }
        } catch (e: Exception) {
            // Class might not exist on older versions
        }

        // Hook CaptureRequest.Builder methods
        try {
            val builderClass = classLoader.loadClass("android.hardware.camera2.CaptureRequest\$Builder")
            builderClass.apply {
                try {
                    hookAddTarget()
                } catch (e: Exception) {
                    Dog.e(TAG, "Failed to register hook: addTarget", e, true)
                }
                try {
                    hookRemoveTarget()
                } catch (e: Exception) {
                    Dog.e(TAG, "Failed to register hook: removeTarget", e, true)
                }
            }
        } catch (e: Exception) {
            Dog.e(TAG, "Failed to load CaptureRequest.Builder", e, true)
        }
    }

    private fun Class<*>.hookCreateCaptureSessionWithSurfaces() {
        val method = try {
            getDeclaredMethod(
                "createCaptureSession",
                List::class.java,
                CameraCaptureSession.StateCallback::class.java,
                Handler::class.java)
        } catch (e: Exception) {
            Dog.e(TAG, "Could not locate hook method for createCaptureSession(List, StateCallback, Handler)", e, true)
            null
        }
        method?.let { m ->
            magic.hook(m).intercept { chain ->
                BlackHoleMapper.clearAll()
                Dog.i(TAG, "[SYSTEM] createCaptureSession(List, Callback, Handler) enter", true)
                Dog.i(TAG, "isReadyForHook check", true)
                if (!SourceManager.isReadyForHook()) return@intercept chain.proceed()
                
                try {
                    val camera = chain.thisObject as CameraDevice
                    val state = getCameraState(camera)
                    activeCameraRef = WeakReference(camera)
                    
                    @Suppress("UNCHECKED_CAST")
                    val surfaces = chain.args[0] as List<Surface>
                    Dog.i(TAG, "Processing ${surfaces.size} surfaces", true)
                    
                    val newList = surfaces.mapTo(ArrayList()) { origin ->
                        val (w, h, f) = NativeBridge.getSurfaceInfo(origin)
                        Dog.i(TAG, "Surface info: origin=$origin, w=$w, h=$h, f=$f", true)
                        
                        if (f == 1 || f == 34 || f == 35 || f == 4 || w > 0) {
                            val blackHoleSurface = BlackHoleMapper.createBlackHole(origin)
                            Dog.i(TAG, "REPLACED surface $origin with $blackHoleSurface (format $f)", true)
                            state.bindSurface(origin)
                            return@mapTo blackHoleSurface
                        }
                        origin
                    }
                    
                    val stateCallback = chain.args[1] as StateCallback
                    Dog.i(TAG, "About to call handleStateCallback", true)
                    try {
                        handleStateCallback(stateCallback)
                        Dog.i(TAG, "handleStateCallback completed", true)
                    } catch (e: Exception) {
                        Dog.e(TAG, "handleStateCallback failed", e, true)
                    }
                    Dog.i(TAG, "About to call checkAndStartVideoPusherFallback", true)
                    try {
                        // Fallback to start VideoPusher if onConfigured doesn't fire
                        checkAndStartVideoPusherFallback(camera, state)
                        Dog.i(TAG, "checkAndStartVideoPusherFallback completed", true)
                    } catch (e: Exception) {
                        Dog.e(TAG, "checkAndStartVideoPusherFallback failed", e, true)
                    }
                    
                    val newArgs = chain.args.toTypedArray()
                    newArgs[0] = newList
                    return@intercept chain.proceed(newArgs)
                } catch (e: Exception) {
                    Dog.e(TAG, "Error in createCaptureSession(List) hook", e, true)
                    return@intercept chain.proceed()
                }
            }
        }
    }
    
    private fun Class<*>.hookCreateCaptureSessionByOutputConfigurations() {
        val method = try {
            getDeclaredMethod(
                "createCaptureSessionByOutputConfigurations",
                List::class.java,
                CameraCaptureSession.StateCallback::class.java,
                Handler::class.java)
        } catch (e: Exception) {
            Dog.e(TAG, "Could not locate hook method for createCaptureSessionByOutputConfigurations", e, true)
            null
        }
        method?.let { m ->
            magic.hook(m).intercept { chain ->
                BlackHoleMapper.clearAll()
                Dog.i(TAG, "[SYSTEM] createCaptureSessionByOutputConfigurations enter", true)
                Dog.i(TAG, "isReadyForHook check", true)
                if (!SourceManager.isReadyForHook()) return@intercept chain.proceed()
                
                try {
                    val camera = chain.thisObject as CameraDevice
                    val state = getCameraState(camera)
                    activeCameraRef = WeakReference(camera)
                    
                    @Suppress("UNCHECKED_CAST")
                    val configs = chain.args[0] as List<OutputConfiguration>
                    Dog.i(TAG, "Processing ${configs.size} output configurations", true)
                    
                    // Create new OutputConfiguration objects instead of modifying existing ones
                    val newConfigs = configs.map { oc ->
                        var modified = false
                        val surfaces = oc.surfaces
                        val modifiedSurfaces = surfaces.mapTo(ArrayList<Surface>()) { origin ->
                            val (w, h, f) = NativeBridge.getSurfaceInfo(origin)
                            Dog.i(TAG, "Surface info: origin=$origin, w=$w, h=$h, f=$f", true)
                            
                            if (f == 1 || f == 34 || f == 35 || f == 4 || w > 0) {
                                modified = true
                                val blackHoleSurface = BlackHoleMapper.createBlackHole(origin)
                                Dog.i(TAG, "REPLACED surface $origin with $blackHoleSurface (format $f)", true)
                                state.bindSurface(origin)
                                return@mapTo blackHoleSurface
                            }
                            origin
                        }
                        
                        // If any surface was modified, create a new OutputConfiguration
                        if (modified) {
                            val newOc = OutputConfiguration(modifiedSurfaces[0])
                            for (i in 1 until modifiedSurfaces.size) {
                                newOc.addSurface(modifiedSurfaces[i])
                            }
                            newOc
                        } else {
                            // No modification needed, keep original
                            oc
                        }
                    }
                    
                    val stateCallback = chain.args[1] as StateCallback
                    Dog.i(TAG, "About to call handleStateCallback", true)
                    try {
                        handleStateCallback(stateCallback)
                        Dog.i(TAG, "handleStateCallback completed", true)
                    } catch (e: Exception) {
                        Dog.e(TAG, "handleStateCallback failed", e, true)
                    }
                    Dog.i(TAG, "About to call checkAndStartVideoPusherFallback", true)
                    try {
                        // Fallback to start VideoPusher if onConfigured doesn't fire
                        checkAndStartVideoPusherFallback(camera, state)
                        Dog.i(TAG, "checkAndStartVideoPusherFallback completed", true)
                    } catch (e: Exception) {
                        Dog.e(TAG, "checkAndStartVideoPusherFallback failed", e, true)
                    }
                    
                    // Proceed with new configurations
                    val newArgs = chain.args.toTypedArray()
                    newArgs[0] = newConfigs
                    return@intercept chain.proceed(newArgs)
                } catch (e: Exception) {
                    Dog.e(TAG, "Error in createCaptureSessionByOutputConfigurations hook", e, true)
                    return@intercept chain.proceed()
                }
            }
        }
    }
    
    private fun Class<*>.hookCreateCaptureSessionWithConfiguration() {
        val method = try {
            getDeclaredMethod("createCaptureSession", SessionConfiguration::class.java)
        } catch (e: Exception) {
            Dog.e(TAG, "Could not locate hook method for createCaptureSession(SessionConfiguration)", e, true)
            null
        }
        method?.let { m ->
            magic.hook(m).intercept { chain ->
                BlackHoleMapper.clearAll()
                Dog.i(TAG, "[SYSTEM] createCaptureSession(SessionConfiguration) enter", true)
                Dog.i(TAG, "isReadyForHook check", true)
                if (!SourceManager.isReadyForHook()) return@intercept chain.proceed()
                
                try {
                    val camera = chain.thisObject as CameraDevice
                    val state = getCameraState(camera)
                    activeCameraRef = WeakReference(camera)
                    
                    val sessionConfiguration = chain.args[0] as SessionConfiguration
                    Dog.i(TAG, "Processing ${sessionConfiguration.outputConfigurations.size} configurations", true)
                    
                    // Create new OutputConfiguration objects instead of modifying existing ones
                    val newConfigs = sessionConfiguration.outputConfigurations.map { oc ->
                        var modified = false
                        val surfaces = oc.surfaces
                        val modifiedSurfaces = surfaces.mapTo(ArrayList<Surface>()) { origin ->
                            val (w, h, f) = NativeBridge.getSurfaceInfo(origin)
                            Dog.i(TAG, "Surface info: origin=$origin, w=$w, h=$h, f=$f", true)
                            
                            if (f == 1 || f == 34 || f == 35 || f == 4 || w > 0) {
                                modified = true
                                val blackHoleSurface = BlackHoleMapper.createBlackHole(origin)
                                Dog.i(TAG, "REPLACED surface $origin with $blackHoleSurface (format $f)", true)
                                state.bindSurface(origin)
                                return@mapTo blackHoleSurface
                            }
                            origin
                        }
                        
                        // If any surface was modified, create a new OutputConfiguration
                        if (modified) {
                            val newOc = OutputConfiguration(modifiedSurfaces[0])
                            for (i in 1 until modifiedSurfaces.size) {
                                newOc.addSurface(modifiedSurfaces[i])
                            }
                            newOc
                        } else {
                            // No modification needed, keep original
                            oc
                        }
                    }
                    
                    // Create new SessionConfiguration with new OutputConfigurations
                    // Use the original executor from the session configuration
                    val originalExecutor = try {
                        val execField = SessionConfiguration::class.java.getDeclaredField("mExecutor")
                        execField.isAccessible = true
                        execField.get(sessionConfiguration) as? java.util.concurrent.Executor
                    } catch (e: Exception) {
                        Dog.w(TAG, "Could not get original executor from SessionConfiguration", true)
                        null
                    }
                    val newSessionConfig = if (originalExecutor != null) {
                        SessionConfiguration(
                            sessionConfiguration.sessionType,
                            ArrayList<OutputConfiguration>(newConfigs),
                            originalExecutor,
                            sessionConfiguration.stateCallback
                        )
                    } else {
                        SessionConfiguration(
                            sessionConfiguration.sessionType,
                            ArrayList<OutputConfiguration>(newConfigs)
                        )
                    }
                    
                    Dog.i(TAG, "About to call handleStateCallback", true)
                    try {
                        handleStateCallback(sessionConfiguration.stateCallback)
                        Dog.i(TAG, "handleStateCallback completed", true)
                    } catch (e: Exception) {
                        Dog.e(TAG, "handleStateCallback failed", e, true)
                    }
                    Dog.i(TAG, "About to call checkAndStartVideoPusherFallback", true)
                    try {
                        // Fallback to start VideoPusher if onConfigured doesn't fire
                        checkAndStartVideoPusherFallback(camera, state)
                        Dog.i(TAG, "checkAndStartVideoPusherFallback completed", true)
                    } catch (e: Exception) {
                        Dog.e(TAG, "checkAndStartVideoPusherFallback failed", e, true)
                    }
                    
                    // Proceed with new session configuration
                    val newArgs = chain.args.toTypedArray()
                    newArgs[0] = newSessionConfig
                    return@intercept chain.proceed(newArgs)
                } catch (e: Exception) {
                    Dog.e(TAG, "Error in createCaptureSession(SessionConfiguration) hook", e, true)
                    return@intercept chain.proceed()
                }
            }
        }
    }

    private fun getCameraState(camera: CameraDevice): CameraState {
        return synchronized(cameraState) {
            cameraState.getOrPut(camera) { CameraState() }
        }
    }

    private fun CameraState.bindSurface(surface: Surface) {
        val (width, height, _) = NativeBridge.getSurfaceInfo(surface)
        // Keep the largest surface as the primary reference for VideoPusher resolution
        if (width * height > this.previewWidth * this.previewHeight) {
            this.previewWidth = width
            this.previewHeight = height
            this.pictureWidth = width
            this.pictureHeight = height
        }
        this.surfaces.add(surface)
    }

    private var videoPusherStartedRef = WeakHashMap<CameraDevice, Boolean>()

    private fun handleStateCallback(callback: StateCallback) {
        @Suppress("ConditionAlwaysTrueFalse")
        if (callback == null) {
            Dog.w(TAG, "handleStateCallback: callback is null!", true)
            return
        }
        val clazz = callback.javaClass
        Dog.i(TAG, "handleStateCallback called for class: ${clazz.simpleName}", true)
        if (hookedClasses.add(clazz)) {
            val onConfigured = try {
                clazz.getDeclaredMethod("onConfigured", CameraCaptureSession::class.java)
            } catch (e: NoSuchMethodException) {
                // Try to find inherited method
                clazz.getMethod("onConfigured", CameraCaptureSession::class.java)
            }

            magic.hook(onConfigured).intercept { chain ->
                try {
                    val session = chain.args[0] as CameraCaptureSession
                    val camera = session.device
                    val state = getCameraState(camera)
                    
                    Dog.i(TAG, "onConfigured called - session: $session, camera: $camera, surfaces count: ${state.surfaces.size}", true)
                    
                    SourceManager.refreshAndDispatch()
                    
                    Handler(Looper.getMainLooper()).postDelayed({ 
                        try {
                            val videoId = SourceManager.getVideoId()
                            Dog.i(TAG, "VideoPusher start check - videoId: $videoId, surfaces not empty: ${state.surfaces.isNotEmpty()}", true)
                            if (videoId != -1L && state.surfaces.isNotEmpty()) {
                                Dog.i(TAG, "Starting VideoPusher on ${state.surfaces.size} surfaces", true)
                                // Protect VideoPusher.start() to avoid propagating errors to the framework
                                VideoPusher.start(state.surfaces, state.previewWidth, state.previewHeight, videoId)
                                videoPusherStartedRef[camera] = true
                            } else {
                                Dog.i(TAG, "NOT starting VideoPusher - videoId: $videoId, surfaces empty: ${state.surfaces.isEmpty()}", true)
                            }
                        } catch (e: Exception) {
                            Dog.e(TAG, "Error while starting VideoPusher", e, true)
                        }
                    }, 100)
                    chain.proceed()
                } catch (e: Exception) {
                    Dog.e(TAG, "Error in onConfigured hook", e, true)
                    chain.proceed()
                }
            }

            val onConfigureFailed = try {
                clazz.getDeclaredMethod("onConfigureFailed", CameraCaptureSession::class.java)
            } catch (e: NoSuchMethodException) {
                // Try to find inherited method
                clazz.getMethod("onConfigureFailed", CameraCaptureSession::class.java)
            }

            magic.hook(onConfigureFailed).intercept { chain ->
                Dog.e(TAG, "CameraCaptureSession.StateCallback: onConfigureFailed for class: ${clazz.simpleName}", null, true)
                BlackHoleMapper.clearAll()
                activeCameraRef = null
                chain.proceed()
            }
        }
    }

    private fun checkAndStartVideoPusherFallback(camera: CameraDevice, state: CameraState) {
        @Suppress("ConditionAlwaysTrueFalse")
        if (camera == null || state == null) {
            Dog.w(TAG, "FALLBACK: camera or state is null", true)
            return
        }
        val alreadyStarted = videoPusherStartedRef[camera] ?: false
        if (!alreadyStarted) {
            Dog.i(TAG, "FALLBACK: Checking if we should start VideoPusher for camera: $camera", true)
            Handler(Looper.getMainLooper()).postDelayed({
                val videoId = SourceManager.getVideoId()
                Dog.i(TAG, "FALLBACK: VideoPusher check - videoId: $videoId, surfaces not empty: ${state.surfaces.isNotEmpty()}", true)
                if (videoId != -1L && state.surfaces.isNotEmpty()) {
                    Dog.i(TAG, "FALLBACK: Starting VideoPusher on ${state.surfaces.size} surfaces (onConfigured didn't fire in time)", true)
                    VideoPusher.start(state.surfaces, state.previewWidth, state.previewHeight, videoId)
                    videoPusherStartedRef[camera] = true
                } else {
                    Dog.i(TAG, "FALLBACK: Not starting VideoPusher - videoId: $videoId, surfaces empty: ${state.surfaces.isEmpty()}", true)
                }
            }, 500) // Reduced from 2000ms to 500ms for faster startup
        }
    }

    private fun Class<*>.hookClose() {
        val close = getMethod("close")
        magic.hook(close).intercept { chain ->
            val activeCamera = activeCameraRef?.get() as? CameraDevice
            val closingCamera = chain.thisObject as CameraDevice

            if (activeCamera != null && closingCamera === activeCamera) {
                Dog.i(TAG, "camera[${System.identityHashCode(closingCamera)}] close.", true)
                Handler(Looper.getMainLooper()).post { 
                    VideoPusher.stop()
                }
                // Don't clear all immediately to avoid switch crash
                activeCameraRef = null
            }
            chain.proceed()
        }
    }

    private fun Class<*>.hookAddTarget() {
        val addTarget = getDeclaredMethod("addTarget", Surface::class.java)
        magic.hook(addTarget).intercept { chain ->
            val origin = chain.args[0] as Surface
            val blackHole = BlackHoleMapper.getBlackHole(origin)
            if (!SourceManager.isReadyForHook() || blackHole == null) {
                return@intercept chain.proceed()
            }
            chain.proceed(arrayOf(blackHole))
        }
    }

    private fun Class<*>.hookRemoveTarget() {
        val removeTarget = getDeclaredMethod("removeTarget", Surface::class.java)
        magic.hook(removeTarget).intercept { chain ->
            val origin = chain.args[0] as Surface
            val blackHole = BlackHoleMapper.getBlackHole(origin)
            if (!SourceManager.isReadyForHook() || blackHole == null) {
                return@intercept chain.proceed()
            }
            chain.proceed(arrayOf(blackHole))
        }
    }
}
