package com.openclaw.alfred.voice

import android.content.Context
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import org.vosk.Model
import org.vosk.Recognizer
import org.vosk.android.RecognitionListener
import org.vosk.android.SpeechService
import org.vosk.android.StorageService
import java.io.File
import java.io.IOException

/**
 * Wake word detector using Vosk for offline, continuous listening.
 * Listens for "hey alfred" or "alfred" to trigger voice input.
 */
class WakeWordDetector(
    private val context: Context,
    private val onWakeWordDetected: () -> Unit,
    private val onError: (String) -> Unit,
    private val onInitialized: () -> Unit = {}
) {
    
    private val TAG = "WakeWordDetector"
    private var model: Model? = null
    private var speechService: SpeechService? = null
    private var isListening = false
    private var detectionPending = false  // Prevent duplicate detections
    
    /**
     * Get wake words from preferences.
     * Returns: ["alfred", "hey alfred", "ok alfred"] (or custom variants)
     */
    private fun getWakeWords(): Set<String> {
        val prefs = context.getSharedPreferences("alfred_settings", android.content.Context.MODE_PRIVATE)
        val customWord = prefs.getString("wake_word", "alfred") ?: "alfred"
        
        return setOf(
            customWord.lowercase().trim(),
            "hey ${customWord.lowercase().trim()}",
            "ok ${customWord.lowercase().trim()}"
        )
    }
    
    /**
     * Initialize the Vosk model (must be called before start).
     * Copies model from assets and loads it.
     */
    suspend fun initialize() {
        withContext(Dispatchers.IO) {
            try {
                Log.d(TAG, "Initializing Vosk model...")
                
                // Target directory in app's internal storage
                val modelDir = File(context.filesDir, "vosk-model")
                
                // Copy model from assets if not already there
                if (!modelDir.exists() || !File(modelDir, "am").exists()) {
                    Log.d(TAG, "Copying model from assets...")
                    copyModelFromAssets(modelDir)
                    Log.d(TAG, "Model copied successfully")
                }
                
                // Load the model
                Log.d(TAG, "Loading model from ${modelDir.absolutePath}")
                model = Model(modelDir.absolutePath)
                Log.d(TAG, "Model loaded successfully")
                
                // Notify success on main thread
                withContext(Dispatchers.Main) {
                    onInitialized()
                }
                
            } catch (e: Exception) {
                Log.e(TAG, "Failed to initialize model", e)
                withContext(Dispatchers.Main) {
                    onError("Wake word setup failed: ${e.message}")
                }
            }
        }
    }
    
    /**
     * Copy model from assets to internal storage.
     */
    private fun copyModelFromAssets(targetDir: File) {
        targetDir.mkdirs()
        
        // List of directories to copy
        val dirs = listOf("am", "conf", "graph", "ivector")
        
        for (dir in dirs) {
            val assetPath = "vosk-model/$dir"
            val targetSubDir = File(targetDir, dir)
            targetSubDir.mkdirs()
            
            // Copy all files in this directory
            val files = context.assets.list(assetPath) ?: continue
            for (file in files) {
                val assetFilePath = "$assetPath/$file"
                val targetFile = File(targetSubDir, file)
                
                // Check if it's a subdirectory
                val subFiles = context.assets.list(assetFilePath)
                if (subFiles != null && subFiles.isNotEmpty()) {
                    // It's a directory, recurse
                    targetFile.mkdirs()
                    for (subFile in subFiles) {
                        copyAssetFile("$assetFilePath/$subFile", File(targetFile, subFile))
                    }
                } else {
                    // It's a file, copy it
                    copyAssetFile(assetFilePath, targetFile)
                }
            }
        }
        
        // Copy README if it exists
        try {
            copyAssetFile("vosk-model/README", File(targetDir, "README"))
        } catch (e: Exception) {
            // README is optional
        }
    }
    
    /**
     * Copy a single file from assets.
     */
    private fun copyAssetFile(assetPath: String, targetFile: File) {
        context.assets.open(assetPath).use { input ->
            targetFile.outputStream().use { output ->
                input.copyTo(output)
            }
        }
    }
    
    /**
     * Start listening for wake words.
     */
    fun start() {
        if (isListening) {
            Log.w(TAG, "Already listening")
            return
        }
        
        val currentModel = model
        if (currentModel == null) {
            onError("Model not initialized. Call initialize() first.")
            return
        }
        
        try {
            // Reset detection flag for new listening session
            detectionPending = false
            
            // Create recognizer for partial results
            val recognizer = Recognizer(currentModel, 16000.0f)
            recognizer.setMaxAlternatives(1)
            recognizer.setWords(false) // Don't need word timestamps for wake word
            
            // Create speech service with recognition listener
            speechService = SpeechService(recognizer, 16000.0f)
            speechService?.startListening(object : RecognitionListener {
                override fun onPartialResult(hypothesis: String?) {
                    hypothesis?.let { checkForWakeWord(it) }
                }
                
                override fun onResult(hypothesis: String?) {
                    hypothesis?.let { checkForWakeWord(it) }
                }
                
                override fun onFinalResult(hypothesis: String?) {
                    // Not used for continuous listening
                }
                
                override fun onError(exception: Exception?) {
                    Log.e(TAG, "Recognition error", exception)
                    onError("Wake word error: ${exception?.message}")
                }
                
                override fun onTimeout() {
                    Log.d(TAG, "Recognition timeout - restarting (continuous mode)")
                    // Immediately restart for continuous listening
                    if (isListening) {
                        start()
                    }
                }
            })
            
            isListening = true
            Log.d(TAG, "Started listening for wake words")
            
        } catch (e: Exception) {
            Log.e(TAG, "Failed to start listening", e)
            onError("Failed to start wake word: ${e.message}")
        }
    }
    
    /**
     * Stop listening for wake words.
     */
    fun stop() {
        if (!isListening) {
            return
        }
        
        try {
            speechService?.stop()
            speechService?.shutdown()
            speechService = null
            isListening = false
            Log.d(TAG, "Stopped listening for wake words")
        } catch (e: Exception) {
            Log.e(TAG, "Error stopping wake word detector", e)
        }
    }
    
    /**
     * Check if the hypothesis contains a wake word.
     */
    private fun checkForWakeWord(hypothesis: String) {
        // Prevent duplicate detections
        if (detectionPending) {
            return
        }
        
        try {
            // Parse JSON hypothesis
            val json = JSONObject(hypothesis)
            val text = json.optString("partial", json.optString("text", ""))
            
            if (text.isNotEmpty()) {
                val lowerText = text.trim().lowercase()
                Log.d(TAG, "Heard: $lowerText")
                
                // Check for wake words (get from preferences)
                val wakeWords = getWakeWords()
                for (wakeWord in wakeWords) {
                    if (lowerText.contains(wakeWord)) {
                        Log.i(TAG, "Wake word detected: $wakeWord")
                        detectionPending = true  // Block further detections
                        onWakeWordDetected()
                        return
                    }
                }
            }
        } catch (e: Exception) {
            Log.e(TAG, "Error parsing hypothesis: $hypothesis", e)
        }
    }
    
    /**
     * Copy folder from assets to storage recursively.
     */
    private fun copyAssetFolderRecursive(assetPath: String, targetPath: File) {
        try {
            val assets = context.assets.list(assetPath)
            
            if (assets == null || assets.isEmpty()) {
                // It's a file, copy it
                targetPath.parentFile?.mkdirs()
                context.assets.open(assetPath).use { input ->
                    targetPath.outputStream().use { output ->
                        input.copyTo(output)
                    }
                }
                Log.d(TAG, "Copied file: $assetPath -> ${targetPath.absolutePath}")
            } else {
                // It's a directory, create it and recurse for each child
                targetPath.mkdirs()
                for (asset in assets) {
                    val subAssetPath = "$assetPath/$asset"
                    val subTargetPath = File(targetPath, asset)
                    copyAssetFolderRecursive(subAssetPath, subTargetPath)
                }
                Log.d(TAG, "Copied directory: $assetPath -> ${targetPath.absolutePath}")
            }
        } catch (e: IOException) {
            Log.e(TAG, "Error copying asset: $assetPath", e)
            throw e
        }
    }
    
    /**
     * Cleanup resources.
     */
    fun destroy() {
        stop()
        model?.close()
        model = null
    }
    
    /**
     * Check if currently listening.
     */
    fun isListening(): Boolean = isListening
}
