package com.openclaw.alfred.service

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import androidx.core.app.NotificationCompat
import com.openclaw.alfred.MainActivity
import com.openclaw.alfred.R
import com.openclaw.alfred.gateway.GatewayClient
import com.openclaw.alfred.gateway.GatewayListener
import com.openclaw.alfred.voice.WakeWordDetector
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import android.os.Handler
import android.os.Looper

/**
 * Foreground service that maintains persistent WebSocket connection to OpenClaw gateway.
 * Survives screen-off and Doze mode.
 */
class AlfredConnectionService : Service() {
    
    private val TAG = "AlfredConnectionService"
    private val CHANNEL_ID = "alfred_connection"
    private val NOTIFICATION_ID = 1001
    
    private var gatewayClient: GatewayClient? = null
    private var wakeLock: PowerManager.WakeLock? = null
    private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    // Wake word detector for continuous listening
    private var wakeWordDetector: WakeWordDetector? = null
    private var wakeWordEnabled = false
    
    // External listener that MainActivity can set
    private var externalListener: GatewayListener? = null
    
    // Track current connection state to notify late-registering listeners
    private var currentConnectionState: ConnectionState = ConnectionState.DISCONNECTED
    
    private enum class ConnectionState {
        DISCONNECTED,
        CONNECTING,
        CONNECTED
    }
    
    // Binder for MainActivity to bind to this service
    private val binder = LocalBinder()
    
    inner class LocalBinder : Binder() {
        fun getService(): AlfredConnectionService = this@AlfredConnectionService
    }
    
    override fun onBind(intent: Intent): IBinder {
        Log.d(TAG, "Service bound")
        return binder
    }
    
    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "Service created")
        createNotificationChannel()
        startForegroundService()
    }
    
    private fun startForegroundService() {
        val assistantName = getAssistantName()
        val channelId = "alfred_connection"
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "Alfred Connection",
                NotificationManager.IMPORTANCE_LOW
            ).apply {
                description = "Maintains connection to Alfred assistant"
                setShowBadge(false)
            }
            val notificationManager = getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(channel)
        }
        
        val notification = NotificationCompat.Builder(this, channelId)
            .setContentTitle(assistantName)
            .setContentText("Starting...")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setOngoing(true)
            .setSilent(true)
            .build()
        
        startForeground(NOTIFICATION_ID, notification)
        Log.d(TAG, "Foreground service started with notification")
    }
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG, "Service started")
        
        val gatewayUrl = intent?.getStringExtra("GATEWAY_URL")
        val accessToken = intent?.getStringExtra("ACCESS_TOKEN")
        val userId = intent?.getStringExtra("USER_ID")
        
        if (gatewayUrl != null && accessToken != null && userId != null) {
            startForeground(NOTIFICATION_ID, createNotification("Connecting..."))
            
            // Only connect if we don't already have a client
            if (gatewayClient == null) {
                Log.d(TAG, "No existing client, creating new connection")
                connectToGateway(gatewayUrl, accessToken, userId)
            } else {
                Log.d(TAG, "Client already exists, skipping duplicate connect")
            }
        }
        
        // Service will be explicitly stopped, not restarted by system
        return START_NOT_STICKY
    }
    
    private fun getAssistantName(): String {
        val prefs = getSharedPreferences("alfred_settings", Context.MODE_PRIVATE)
        return prefs.getString("assistant_name", "Alfred") ?: "Alfred"
    }
    
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "Alfred Connection",
                NotificationManager.IMPORTANCE_LOW
            ).apply {
                description = "Maintains connection to Alfred assistant"
                setShowBadge(false)
            }
            
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
    
    private fun createNotification(status: String): Notification {
        val assistantName = getAssistantName()
        val notificationIntent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            notificationIntent,
            PendingIntent.FLAG_IMMUTABLE
        )
        
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(assistantName)
            .setContentText(status)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentIntent(pendingIntent)
            .setOngoing(true)
            .setSilent(true)
            .build()
    }
    
    private fun updateNotification(text: String) {
        val assistantName = getAssistantName()
        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(assistantName)
            .setContentText(text)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setOngoing(true)
            .setSilent(true)
            .build()
        
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.notify(NOTIFICATION_ID, notification)
    }
    
    private fun createForwardingListener(): GatewayListener {
        return object : GatewayListener {
                override fun onConnecting() {
                    Log.d(TAG, "Gateway connecting")
                    currentConnectionState = ConnectionState.CONNECTING
                    updateNotification("Connecting...")
                    externalListener?.onConnecting()
                }
                
                override fun onConnected() {
                    Log.d(TAG, "Gateway connected")
                    currentConnectionState = ConnectionState.CONNECTED
                    updateNotification("Connected")
                    externalListener?.onConnected()
                }
                
                override fun onDisconnected() {
                    Log.d(TAG, "Gateway disconnected")
                    currentConnectionState = ConnectionState.DISCONNECTED
                    updateNotification("Disconnected")
                    externalListener?.onDisconnected()
                }
                
                override fun onReconnecting(attempt: Int, delayMs: Long) {
                    Log.d(TAG, "Gateway reconnecting: attempt $attempt, delay ${delayMs}ms")
                    updateNotification("Reconnecting...")
                    externalListener?.onReconnecting(attempt, delayMs)
                }
                
                override fun onError(error: String) {
                    Log.e(TAG, "Gateway error: $error")
                    updateNotification("Error")
                    externalListener?.onError(error)
                }
                
                override fun onEvent(event: String, payload: String) {
                    Log.d(TAG, "Event received in service: $event")
                    externalListener?.onEvent(event, payload)
                }
                
                override fun onResponse(id: String, payload: String) {
                    Log.d(TAG, "Response received in service: $id")
                    externalListener?.onResponse(id, payload)
                }
                
                override fun onMessage(sender: String, text: String) {
                    Log.d(TAG, "Message received in service from $sender: ${text.take(100)}")
                    externalListener?.onMessage(sender, text)
                }
                
                override fun onNotification(
                    notificationType: String,
                    title: String,
                    message: String,
                    priority: String,
                    sound: Boolean,
                    vibrate: Boolean,
                    timestamp: Long,
                    action: String?
                ) {
                    Log.d(TAG, "Notification received in service: $notificationType - $title")
                    externalListener?.onNotification(notificationType, title, message, priority, sound, vibrate, timestamp, action)
                }
                
                override fun onAlarmDismissed(alarmId: String) {
                    Log.d(TAG, "Alarm dismissed in service: $alarmId")
                    externalListener?.onAlarmDismissed(alarmId)
                }
                
                override fun onWakeWordDetected() {
                    Log.d(TAG, "Wake word detected (forwarding to external listener)")
                    externalListener?.onWakeWordDetected()
                }
            }
    }
    
    private fun connectToGateway(url: String, token: String, userId: String) {
        Log.d(TAG, "Connecting to gateway")
        
        gatewayClient = GatewayClient(
            context = this,
            accessToken = token,
            listener = createForwardingListener()
        )
        
        gatewayClient?.connect()
    }
    
    /**
     * Set external listener that will receive all gateway events.
     * Prevents duplicate registration by checking if the same listener is already set.
     * Immediately notifies new listener of current connection state.
     */
    fun setListener(listener: GatewayListener?) {
        if (externalListener != null && listener != null) {
            Log.w(TAG, "External listener already set, clearing old one first")
            externalListener = null
        }
        Log.d(TAG, "External listener ${if (listener != null) "set" else "cleared"}")
        Log.d(TAG, "Current connection state: $currentConnectionState")
        externalListener = listener
        
        // Immediately notify new listener of current state
        if (listener != null) {
            when (currentConnectionState) {
                ConnectionState.CONNECTING -> {
                    Log.d(TAG, "Notifying new listener of CONNECTING state")
                    listener.onConnecting()
                }
                ConnectionState.CONNECTED -> {
                    Log.d(TAG, "Notifying new listener of CONNECTED state")
                    listener.onConnected()
                }
                ConnectionState.DISCONNECTED -> {
                    Log.d(TAG, "Not notifying new listener (state is DISCONNECTED)")
                }
            }
        }
    }
    
    /**
     * Get the gateway client for MainActivity to interact with.
     */
    fun getGatewayClient(): GatewayClient? = gatewayClient
    
    /**
     * Reconnect with a new access token (after token refresh).
     */
    fun reconnectWithToken(newToken: String) {
        Log.d(TAG, "Reconnecting with new token")
        gatewayClient?.disconnect()
        
        // Recreate client with new token
        gatewayClient = GatewayClient(
            context = this,
            accessToken = newToken,
            listener = createForwardingListener()
        )
        
        gatewayClient?.connect()
    }
    
    /**
     * Acquire partial wake lock for active conversation mode.
     */
    fun acquireWakeLock() {
        if (wakeLock?.isHeld != true) {
            val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
            wakeLock = powerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK,
                "Alfred::ConversationWakeLock"
            )
            wakeLock?.acquire(10 * 60 * 1000L) // 10 minute timeout
            Log.d(TAG, "Wake lock acquired")
        }
    }
    
    /**
     * Release wake lock when conversation ends.
     */
    fun releaseWakeLock() {
        if (wakeLock?.isHeld == true) {
            wakeLock?.release()
            wakeLock = null
            Log.d(TAG, "Wake lock released")
        }
    }
    
    /**
     * Start wake word detection for continuous listening.
     */
    fun startWakeWord() {
        Log.d(TAG, "startWakeWord called")
        
        if (wakeWordDetector == null) {
            Log.d(TAG, "Creating wake word detector")
            wakeWordDetector = WakeWordDetector(
                context = this,
                onWakeWordDetected = {
                    Log.d(TAG, "Wake word detected in service")
                    // IMMEDIATELY stop the wake word detector to prevent duplicate detections
                    wakeWordDetector?.stop()
                    updateNotification("Connected")
                    // Notify MainScreen via callback
                    externalListener?.onWakeWordDetected()
                },
                onError = { error ->
                    Log.w(TAG, "Wake word error (auto-restarting): $error")
                    // Auto-restart on error (except permissions)
                    if (!error.contains("permission", ignoreCase = true)) {
                        Handler(Looper.getMainLooper()).postDelayed({
                            if (wakeWordEnabled) {
                                Log.d(TAG, "Auto-restarting wake word after error")
                                wakeWordDetector?.start()
                            }
                        }, 1000)
                    }
                },
                onInitialized = {
                    Log.d(TAG, "Wake word initialized in service")
                    wakeWordDetector?.start()
                    updateNotification("Listening for wake word...")
                }
            )
            
            // Initialize for the first time
            serviceScope.launch {
                wakeWordDetector?.initialize()
            }
        } else {
            // Detector already exists, just restart it
            Log.d(TAG, "Wake word detector already exists, restarting")
            if (!wakeWordDetector!!.isListening()) {
                wakeWordDetector?.start()
                updateNotification("Listening for wake word...")
            } else {
                Log.d(TAG, "Wake word detector already listening")
            }
        }
        
        wakeWordEnabled = true
        Log.d(TAG, "Wake word enabled")
    }
    
    /**
     * Stop wake word detection.
     */
    fun stopWakeWord() {
        Log.d(TAG, "Stopping wake word")
        wakeWordDetector?.stop()
        wakeWordEnabled = false
        updateNotification("Connected")
    }
    
    override fun onDestroy() {
        Log.d(TAG, "Service destroyed")
        wakeWordDetector?.destroy()
        wakeWordDetector = null
        gatewayClient?.disconnect()
        gatewayClient = null
        releaseWakeLock()
        serviceScope.cancel()
        super.onDestroy()
    }
    
    companion object {
        /**
         * Start the foreground service.
         */
        fun start(context: Context, gatewayUrl: String, accessToken: String, userId: String) {
            val intent = Intent(context, AlfredConnectionService::class.java).apply {
                putExtra("GATEWAY_URL", gatewayUrl)
                putExtra("ACCESS_TOKEN", accessToken)
                putExtra("USER_ID", userId)
            }
            
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                context.startForegroundService(intent)
            } else {
                context.startService(intent)
            }
        }
        
        /**
         * Stop the foreground service.
         */
        fun stop(context: Context) {
            val intent = Intent(context, AlfredConnectionService::class.java)
            context.stopService(intent)
        }
    }
}
