package com.openclaw.alfred.voice

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.util.Log
import androidx.core.content.ContextCompat
import kotlinx.coroutines.*
import org.vosk.Model
import org.vosk.Recognizer
import org.vosk.android.RecognitionListener
import org.vosk.android.SpeechService
import org.vosk.android.StorageService
import org.json.JSONObject
import java.io.File

/**
 * Manages wake word detection using Vosk.
 * Listens for "alfred" or "hey alfred" to trigger voice input.
 */
class WakeWordManager(
    private val context: Context,
    private val onWakeWordDetected: (fullText: String?) -> Unit,
    private val onError: (String) -> Unit,
    private val onListeningStateChanged: (Boolean) -> Unit
) {
    
    private val TAG = "WakeWordManager"
    private var speechService: SpeechService? = null
    private var model: Model? = null
    private var isListening = false
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    /**
     * Get wake words from preferences.
     */
    private fun getWakeWords(): List<String> {
        val prefs = context.getSharedPreferences("alfred_settings", android.content.Context.MODE_PRIVATE)
        val customWord = prefs.getString("wake_word", "alfred") ?: "alfred"
        
        return listOf(
            customWord.lowercase().trim(),
            "hey ${customWord.lowercase().trim()}",
            "ok ${customWord.lowercase().trim()}"
        )
    }
    
    /**
     * Initialize Vosk model.
     * Model should be in assets/vosk-model-small-en-us-0.15/
     */
    fun initialize(onReady: () -> Unit) {
        scope.launch {
            try {
                Log.d(TAG, "Initializing Vosk model...")
                
                // Unpack model from assets to storage if needed
                val modelPath = initModel()
                
                if (modelPath == null) {
                    withContext(Dispatchers.Main) {
                        onError("Vosk model not found in assets. Please download vosk-model-small-en-us-0.15")
                    }
                    return@launch
                }
                
                // Load the model
                model = Model(modelPath)
                
                Log.d(TAG, "Vosk model initialized successfully")
                withContext(Dispatchers.Main) {
                    onReady()
                }
                
            } catch (e: Exception) {
                Log.e(TAG, "Failed to initialize Vosk", e)
                withContext(Dispatchers.Main) {
                    onError("Failed to initialize wake word: ${e.message}")
                }
            }
        }
    }
    
    /**
     * Unpack model from assets to internal storage.
     */
    private suspend fun initModel(): String? {
        return withContext(Dispatchers.IO) {
            try {
                StorageService.unpack(
                    context,
                    "vosk-model-small-en-us-0.15",
                    "model",
                    { model ->
                        Log.d(TAG, "Model unpacked successfully")
                    },
                    { exception ->
                        Log.e(TAG, "Failed to unpack model", exception)
                    }
                )
                
                // Return path to unpacked model
                val modelDir = File(context.filesDir, "model")
                if (modelDir.exists()) {
                    modelDir.absolutePath
                } else {
                    null
                }
            } catch (e: Exception) {
                Log.e(TAG, "Error unpacking model", e)
                null
            }
        }
    }
    
    /**
     * Start listening for wake word.
     */
    fun startListening() {
        if (isListening) {
            Log.w(TAG, "Already listening")
            return
        }
        
        if (model == null) {
            onError("Model not initialized. Call initialize() first.")
            return
        }
        
        // Check microphone permission
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) 
            != PackageManager.PERMISSION_GRANTED) {
            onError("Microphone permission not granted")
            return
        }
        
        try {
            // Create recognizer for continuous listening
            val recognizer = Recognizer(model, 16000.0f)
            
            // Set up grammar for wake words (improves accuracy and reduces false positives)
            recognizer.setGrammar(
                "[\"alfred\", \"hey alfred\", \"ok alfred\", " +
                "\"[unk]\"]"
            )
            
            // Create speech service
            speechService = SpeechService(recognizer, 16000.0f)
            
            speechService?.startListening(object : RecognitionListener {
                override fun onPartialResult(hypothesis: String?) {
                    // Check for wake word in partial results
                    hypothesis?.let { checkForWakeWord(it, partial = true) }
                }
                
                override fun onResult(hypothesis: String?) {
                    // Check for wake word in final results
                    hypothesis?.let { checkForWakeWord(it, partial = false) }
                }
                
                override fun onFinalResult(hypothesis: String?) {
                    // Final result
                    hypothesis?.let { checkForWakeWord(it, partial = false) }
                }
                
                override fun onError(exception: Exception?) {
                    Log.e(TAG, "Recognition error", exception)
                    onError("Wake word detection error: ${exception?.message}")
                }
                
                override fun onTimeout() {
                    Log.d(TAG, "Recognition timeout")
                }
            })
            
            isListening = true
            onListeningStateChanged(true)
            Log.d(TAG, "Started listening for wake word")
            
        } catch (e: Exception) {
            Log.e(TAG, "Failed to start listening", e)
            onError("Failed to start wake word detection: ${e.message}")
        }
    }
    
    /**
     * Stop listening for wake word.
     */
    fun stopListening() {
        if (!isListening) {
            return
        }
        
        try {
            speechService?.stop()
            speechService?.shutdown()
            speechService = null
            
            isListening = false
            onListeningStateChanged(false)
            Log.d(TAG, "Stopped listening for wake word")
            
        } catch (e: Exception) {
            Log.e(TAG, "Error stopping listening", e)
        }
    }
    
    /**
     * Check if recognized text contains a wake word.
     */
    private fun checkForWakeWord(hypothesis: String, partial: Boolean) {
        try {
            val json = JSONObject(hypothesis)
            val text = json.optString("text", "").lowercase().trim()
            
            if (text.isEmpty()) {
                return
            }
            
            Log.d(TAG, "Recognized (partial=$partial): $text")
            
            // Check if any wake word is present (get from preferences)
            val wakeWords = getWakeWords()
            for (wakeWord in wakeWords) {
                if (text.contains(wakeWord)) {
                    Log.i(TAG, "Wake word detected: $wakeWord in '$text'")
                    
                    // Extract the command after the wake word (if any)
                    val commandText = extractCommandAfterWakeWord(text, wakeWord)
                    
                    // Trigger callback
                    onWakeWordDetected(commandText)
                    
                    // For now, stop listening after wake word (prevents repeated triggers)
                    // Could be made configurable for continuous listening
                    stopListening()
                    
                    break
                }
            }
        } catch (e: Exception) {
            Log.e(TAG, "Error parsing hypothesis", e)
        }
    }
    
    /**
     * Extract the command text after the wake word.
     * E.g., "hey alfred what's the weather" -> "what's the weather"
     */
    private fun extractCommandAfterWakeWord(text: String, wakeWord: String): String? {
        val index = text.indexOf(wakeWord)
        if (index < 0) {
            return null
        }
        
        val afterWakeWord = text.substring(index + wakeWord.length).trim()
        return if (afterWakeWord.isNotEmpty()) afterWakeWord else null
    }
    
    /**
     * Check if currently listening.
     */
    fun isListening(): Boolean = isListening
    
    /**
     * Cleanup resources.
     */
    fun destroy() {
        stopListening()
        model?.close()
        model = null
        scope.cancel()
    }
}
