package com.nexlab.openclaw.node.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.content.pm.PackageManager
import android.os.BatteryManager
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.provider.Settings
import android.util.Log
import androidx.core.app.NotificationCompat
import com.nexlab.openclaw.node.R
import com.nexlab.openclaw.node.data.remote.NodeClient
import com.nexlab.openclaw.node.data.repository.SettingsRepository
import com.nexlab.openclaw.node.domain.model.*
import com.nexlab.openclaw.node.ui.MainActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.first
import javax.inject.Inject

private const val TAG = "NodeService"

class NodeService : Service() {

    // Initialize directly - not nullable
    private val nodeClient = NodeClient()

    private val CHANNEL_ID = "openclaw_node_channel"
    private val NOTIFICATION_ID = 1

    private var wakeLock: PowerManager.WakeLock? = null
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    private val screenshotService by lazy { ScreenshotService(this) }
    
    override fun onBind(intent: Intent?): IBinder? = null

    override fun onCreate() {
        super.onCreate()
        
        // Initialize NodeClient with context for device identity persistence
        nodeClient.setContext(this)
        
        // NodeClient is initialized directly above
        // Debug: Write to file
        try {
            val debugFile = java.io.File(cacheDir, "nodeservice_debug.txt")
            debugFile.writeText("onCreate at ${System.currentTimeMillis()}\n")
        } catch (e: Exception) {}
        
        Log.e(TAG, "NodeService onCreate called!")
        createNotificationChannel()
        acquireWakeLock()
        
        // Auto-start with saved settings OR hardcoded defaults
        try {
            val prefs = getSharedPreferences("openclaw_settings", MODE_PRIVATE)
            Log.e(TAG, "Reading SharedPreferences...")
            // Use SharedPreferences or fall back to hardcoded defaults
            var ip = prefs.getString("gateway_ip", "") ?: ""
            var port = prefs.getString("gateway_port", "18789") ?: "18789"
            var token = prefs.getString("node_token", "") ?: ""
            var protocol = prefs.getString("gateway_protocol", "WS") ?: "WS"
            var nodeId = prefs.getString("node_id", "") ?: ""
            
            // TEMPORARY HARDCODED CONFIG FOR PAIRING TESTING
            if (ip.isBlank()) {
                Log.e(TAG, "Using hardcoded config for pairing test")
                ip = "192.168.11.46"
                port = "18789"
                token = "415fa3c21b7ef06f22aff571697d88c59c2dc67737681267"
                protocol = "WS"
                nodeId = "android-node"
            }
            // END TEMPORARY CONFIG
            
            Log.e(TAG, "Settings read: ip=$ip tokenLen=${token.length} port=$port")
            
            if (ip.isNotBlank() && token.isNotBlank()) {
                val fullUrl = if (protocol == "WSS") "wss://$ip:$port" else "ws://$ip:$port"
                Log.e(TAG, "Auto-connecting to: $fullUrl")
                // Removed Toast - was causing crash on background thread
                scope.launch { startNode(fullUrl, token, nodeId) }
            } else {
                Log.e(TAG, "No settings found, not connecting")
            }
        } catch (e: Exception) {
            Log.e(TAG, "Auto-start error: ${e.message}")
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Debug: Write to file
        try {
            val debugFile = java.io.File(cacheDir, "nodeservice_debug.txt")
            debugFile.appendText("onStartCommand at ${System.currentTimeMillis()} intent=$intent\n")
        } catch (e: Exception) {}
        
        try {
        when (intent?.action) {
            "START" -> {
                startForeground(NOTIFICATION_ID, createNotification())
                // Get settings from Intent extras
                var protocol = intent.getStringExtra("gateway_protocol") ?: "WS"
                var ip = intent.getStringExtra("gateway_ip") ?: ""
                var port = intent.getStringExtra("gateway_port") ?: "18789"
                var token = intent.getStringExtra("node_token") ?: ""
                var nodeId = intent.getStringExtra("node_id") ?: ""
                
                // TEMPORARY HARDCODED CONFIG FOR PAIRING TESTING
                if (ip.isBlank()) {
                    Log.e(TAG, "Using hardcoded config for pairing test")
                    ip = "192.168.11.46"
                    port = "18789"
                    token = "415fa3c21b7ef06f22aff571697d88c59c2dc67737681267"
                    protocol = "WS"
                    nodeId = "android-node"
                }
                // END TEMPORARY CONFIG
                
                val fullUrl = if (protocol == "WSS") "wss://$ip:$port" else "ws://$ip:$port"
                System.out.println("=== NodeService STARTED with URL: $fullUrl ===")
                Log.d(TAG, "Starting with URL: $fullUrl, token present: ${token.isNotBlank()}")
                
                // Debug: Write to file
                try {
                    val debugFile = java.io.File(cacheDir, "nodeservice_debug.txt")
                    debugFile.appendText("START action: url=$fullUrl tokenLen=${token.length} nodeId=$nodeId\n")
                } catch (e: Exception) {}
                
                // Show toast for debugging
                try {
                    Log.e(TAG, "Starting: $fullUrl")
                } catch (e: Exception) {}
                
                scope.launch { startNode(fullUrl, token, nodeId) }
            }
            "STOP" -> {
                scope.launch { stopNode() }
            }
        }
        } catch (e: Exception) {
            try {
                val debugFile = java.io.File(cacheDir, "nodeservice_debug.txt")
                debugFile.appendText("ERROR: ${e.message}\n")
            } catch (ee: Exception) {}
        }
        return START_STICKY
    }

    private suspend fun startNode(gatewayUrl: String, nodeToken: String, nodeId: String) {
        System.out.println("=== NodeService.startNode() CALLED ===")
        Log.d(TAG, "startNode() called with gatewayUrl=$gatewayUrl, nodeId=$nodeId, tokenPresent=${nodeToken.isNotBlank()}")

        if (gatewayUrl.isBlank() || nodeToken.isBlank()) {
            System.out.println("=== Blank gatewayUrl or token, returning early ===")
            Log.w(TAG, "Blank gatewayUrl or token, returning early")
            Log.e(TAG, "Toast removed")
            return
        }

        // Show connecting toast
        Log.e(TAG, "Toast removed")

        // Setup notification forwarding
        NotificationListener.onNotificationReceived = { app, title, text ->
            nodeClient.sendEvent(
                NodeEvent.Notification(app, title, text, System.currentTimeMillis())
            )
        }

        // Connect to gateway
        nodeClient.connect(
            gatewayUrl = gatewayUrl,
            nodeId = nodeId,
            token = nodeToken,
            capabilities = NodeCapabilities(
                screenshot = true,
                touch = true,
                shell = true,
                notifications = true,
                appControl = true,
                sensors = true
            )
        )
        
        // Show toast after connect attempt
        Log.e(TAG, "Toast removed")
        
        // Observe connection status
        nodeClient.connectionStatus.collect { status ->
            if (status.connected) {
                Log.e(TAG, "Toast removed")
            } else {
                Log.e(TAG, "Toast removed")
            }
        }

        // Process incoming commands
        nodeClient.incomingCommands.collect { command ->
            handleCommand(command)
        }
    }

    private suspend fun stopNode() {
        nodeClient.disconnect()
        NotificationListener.onNotificationReceived = null
        stopForeground(STOP_FOREGROUND_REMOVE)
        stopSelf()
    }

    private fun handleCommand(command: NodeCommand) {
        when (command) {
            is NodeCommand.Screenshot -> handleScreenshot(command)
            is NodeCommand.Tap -> handleTap(command)
            is NodeCommand.Swipe -> handleSwipe(command)
            is NodeCommand.LongPress -> handleLongPress(command)
            is NodeCommand.LaunchApp -> handleLaunchApp(command)
            is NodeCommand.Shell -> handleShell(command)
            is NodeCommand.DeviceInfo -> handleDeviceInfo(command)
            is NodeCommand.ListApps -> handleListApps(command)
            is NodeCommand.GetForegroundApp -> handleForegroundApp(command)
            is NodeCommand.NotificationListener -> handleNotificationListener(command)
            is NodeCommand.KeyEvent -> handleKeyEvent(command)
            is NodeCommand.Ping -> nodeClient.sendResponse(NodeResponse.Success(command.id))
        }
    }

    private fun handleScreenshot(command: NodeCommand.Screenshot) {
        screenshotService.captureScreenshot(command.quality) { result ->
            result.fold(
                onSuccess = { base64 ->
                    nodeClient.sendResponse(NodeResponse.ScreenshotResponse(command.id, base64))
                },
                onFailure = { error ->
                    nodeClient.sendResponse(NodeResponse.Error(command.id, error.message ?: "Screenshot failed"))
                }
            )
        }
    }

    private fun handleTap(command: NodeCommand.Tap) {
        val success = TouchService.instance?.tap(command.x, command.y) ?: false
        nodeClient.sendResponse(
            if (success) NodeResponse.Success(command.id)
            else NodeResponse.Error(command.id, "Touch service not available")
        )
    }

    private fun handleSwipe(command: NodeCommand.Swipe) {
        val success = TouchService.instance?.swipe(
            command.x1, command.y1, command.x2, command.y2, command.duration
        ) ?: false
        nodeClient.sendResponse(
            if (success) NodeResponse.Success(command.id)
            else NodeResponse.Error(command.id, "Touch service not available")
        )
    }

    private fun handleLongPress(command: NodeCommand.LongPress) {
        val success = TouchService.instance?.longPress(
            command.x, command.y, command.duration
        ) ?: false
        nodeClient.sendResponse(
            if (success) NodeResponse.Success(command.id)
            else NodeResponse.Error(command.id, "Touch service not available")
        )
    }

    private fun handleLaunchApp(command: NodeCommand.LaunchApp) {
        try {
            val intent = packageManager.getLaunchIntentForPackage(command.packageName)
            if (intent != null) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
                nodeClient.sendResponse(NodeResponse.Success(command.id))
            } else {
                nodeClient.sendResponse(NodeResponse.Error(command.id, "App not found"))
            }
        } catch (e: Exception) {
            nodeClient.sendResponse(NodeResponse.Error(command.id, e.message ?: "Failed to launch app"))
        }
    }

    private fun handleShell(command: NodeCommand.Shell) {
        // Limited shell - security critical
        val allowedCommands = listOf(
            "ls", "pwd", "date", "id", "getprop", "settings get",
            "am start", "input tap", "input swipe", "input text"
        )
        
        val isAllowed = allowedCommands.any { command.command.startsWith(it) }
        
        if (isAllowed) {
            try {
                val process = Runtime.getRuntime().exec(command.command)
                val output = process.inputStream.bufferedReader().readText()
                nodeClient.sendResponse(NodeResponse.Success(command.id, mapOf("output" to output)))
            } catch (e: Exception) {
                nodeClient.sendResponse(NodeResponse.Error(command.id, e.message ?: "Shell execution failed"))
            }
        } else {
            nodeClient.sendResponse(NodeResponse.Error(command.id, "Command not allowed"))
        }
    }

    private fun handleDeviceInfo(command: NodeCommand.DeviceInfo) {
        val batteryLevel = 50 // Placeholder - would need proper battery API
        
        nodeClient.sendResponse(NodeResponse.Success(command.id, mapOf(
            "model" to Build.MODEL,
            "manufacturer" to Build.MANUFACTURER,
            "android_version" to Build.VERSION.RELEASE,
            "sdk_int" to Build.VERSION.SDK_INT,
            "battery" to batteryLevel,
            "charging" to false
        )))
    }

    private fun getBatteryLevel(): Int {
        return 50 // Placeholder - would need proper battery API
    }

    private fun handleListApps(command: NodeCommand.ListApps) {
        val pm = packageManager
        val packages = pm.getInstalledApplications(PackageManager.GET_META_DATA)
        
        val apps = packages
            .filter { !command.systemApps || !isSystemApp(it) }
            .map {
                mapOf(
                    "package" to it.packageName,
                    "name" to pm.getApplicationLabel(it).toString()
                )
            }
        
        nodeClient.sendResponse(NodeResponse.Success(command.id, mapOf("apps" to apps)))
    }

    private fun isSystemApp(appInfo: android.content.pm.ApplicationInfo): Boolean {
        return (appInfo.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0
    }

    private fun handleForegroundApp(command: NodeCommand.GetForegroundApp) {
        // Requires usage stats permission - placeholder
        nodeClient.sendResponse(NodeResponse.Error(command.id, "Not implemented - requires Usage Stats permission"))
    }

    private fun handleNotificationListener(command: NodeCommand.NotificationListener) {
        // Would enable/disable notification forwarding
        // Save to SharedPreferences directly instead of using repository
        getSharedPreferences("openclaw_settings", MODE_PRIVATE)
            .edit()
            .putBoolean("notifications_enabled", command.enabled)
            .apply()
        scope.launch {
            nodeClient.sendResponse(NodeResponse.Success(command.id))
        }
    }

    private fun handleKeyEvent(command: NodeCommand.KeyEvent) {
        try {
            val intent = Intent(Intent.ACTION_MEDIA_BUTTON).apply {
                putExtra(Intent.EXTRA_KEY_EVENT, android.view.KeyEvent(
                    android.view.KeyEvent.ACTION_DOWN, command.keyCode
                ))
            }
            sendOrderedBroadcast(intent, null)
            nodeClient.sendResponse(NodeResponse.Success(command.id))
        } catch (e: Exception) {
            nodeClient.sendResponse(NodeResponse.Error(command.id, e.message ?: "Key event failed"))
        }
    }

    private fun createNotificationChannel() {
        val channel = NotificationChannel(
            CHANNEL_ID,
            "OpenClaw Node",
            NotificationManager.IMPORTANCE_LOW
        ).apply {
            description = "OpenClaw Android Node Service"
        }
        val manager = getSystemService(NotificationManager::class.java)
        manager.createNotificationChannel(channel)
    }

    private fun createNotification(): Notification {
        val intent = Intent(this, MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            this, 0, intent,
            PendingIntent.FLAG_IMMUTABLE
        )

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("OpenClaw Node")
            .setContentText("Connected and ready")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentIntent(pendingIntent)
            .setOngoing(true)
            .build()
    }

    private fun acquireWakeLock() {
        val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
        wakeLock = powerManager.newWakeLock(
            PowerManager.PARTIAL_WAKE_LOCK,
            "OpenClaw:NodeWakeLock"
        ).apply {
            acquire(10 * 60 * 60 * 1000L) // 10 hours max
        }
    }

    private fun releaseWakeLock() {
        wakeLock?.let {
            if (it.isHeld) {
                it.release()
            }
        }
        wakeLock = null
    }

    override fun onDestroy() {
        super.onDestroy()
        scope.cancel()
        nodeClient.disconnect()
        releaseWakeLock()
    }
}
