package com.inou.clawdnode.protocol

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.annotations.SerializedName

/**
 * Protocol messages between ClawdNode and Gateway.
 * 
 * Gateway Protocol v3:
 * - Connect to /ws endpoint
 * - Receive connect.challenge event with nonce
 * - Send connect request with role, caps, commands, auth
 * - Receive hello-ok response
 * 
 * Phone → Gateway: Events (notifications, calls, etc.)
 * Gateway → Phone: Commands (answer call, screenshot, etc.)
 */

// ============================================
// PROTOCOL CONSTANTS
// ============================================

object Protocol {
    const val VERSION = 3
    const val CLIENT_ID = "clawdbot-android"
    const val PLATFORM = "android"
    const val MODE = "node"
    const val ROLE = "node"
}

// ============================================
// HANDSHAKE MESSAGES
// ============================================

/**
 * Protocol frame types
 */
enum class FrameType {
    @SerializedName("req") REQUEST,
    @SerializedName("res") RESPONSE,
    @SerializedName("event") EVENT
}

/**
 * Base protocol frame
 */
data class ProtocolFrame(
    @SerializedName("type") val type: String,
    @SerializedName("id") val id: String? = null,
    @SerializedName("method") val method: String? = null,
    @SerializedName("event") val event: String? = null,
    @SerializedName("params") val params: JsonObject? = null,
    @SerializedName("payload") val payload: JsonObject? = null,
    @SerializedName("ok") val ok: Boolean? = null,
    @SerializedName("error") val error: JsonObject? = null
) {
    companion object {
        fun fromJson(json: String): ProtocolFrame? {
            return try {
                Gson().fromJson(json, ProtocolFrame::class.java)
            } catch (e: Exception) {
                null
            }
        }
    }
}

/**
 * Connect challenge event payload
 * Gateway → Client (first message after connect)
 */
data class ConnectChallengePayload(
    @SerializedName("nonce") val nonce: String,
    @SerializedName("ts") val timestamp: Long
)

/**
 * Client info for connect request
 */
data class ClientInfo(
    @SerializedName("id") val id: String = Protocol.CLIENT_ID,
    @SerializedName("version") val version: String,
    @SerializedName("platform") val platform: String = Protocol.PLATFORM,
    @SerializedName("mode") val mode: String = Protocol.MODE
)

/**
 * Device identity for connect request
 */
data class DeviceInfo(
    @SerializedName("id") val id: String,
    @SerializedName("publicKey") val publicKey: String? = null,
    @SerializedName("signature") val signature: String? = null,
    @SerializedName("signedAt") val signedAt: Long? = null,
    @SerializedName("nonce") val nonce: String? = null
)

/**
 * Auth info for connect request
 */
data class AuthInfo(
    @SerializedName("token") val token: String
)

/**
 * Connect request parameters
 */
data class ConnectParams(
    @SerializedName("minProtocol") val minProtocol: Int = Protocol.VERSION,
    @SerializedName("maxProtocol") val maxProtocol: Int = Protocol.VERSION,
    @SerializedName("client") val client: ClientInfo,
    @SerializedName("role") val role: String = Protocol.ROLE,
    @SerializedName("scopes") val scopes: List<String> = emptyList(),
    @SerializedName("caps") val caps: List<String>,
    @SerializedName("commands") val commands: List<String>,
    @SerializedName("permissions") val permissions: Map<String, Boolean> = emptyMap(),
    @SerializedName("auth") val auth: AuthInfo,
    @SerializedName("locale") val locale: String = "en-US",
    @SerializedName("userAgent") val userAgent: String,
    @SerializedName("device") val device: DeviceInfo
)

/**
 * Connect request frame
 */
data class ConnectRequest(
    @SerializedName("type") val type: String = "req",
    @SerializedName("id") val id: String,
    @SerializedName("method") val method: String = "connect",
    @SerializedName("params") val params: ConnectParams
) {
    fun toJson(): String = Gson().toJson(this)
}

/**
 * Hello-ok response payload
 */
data class HelloOkPayload(
    @SerializedName("type") val type: String, // "hello-ok"
    @SerializedName("protocol") val protocol: Int,
    @SerializedName("policy") val policy: PolicyInfo? = null,
    @SerializedName("auth") val auth: AuthResponse? = null
)

data class PolicyInfo(
    @SerializedName("tickIntervalMs") val tickIntervalMs: Long? = null
)

data class AuthResponse(
    @SerializedName("deviceToken") val deviceToken: String? = null,
    @SerializedName("role") val role: String? = null,
    @SerializedName("scopes") val scopes: List<String>? = null
)

// ============================================
// NODE INVOKE (Gateway → Phone)
// ============================================

/**
 * Node invoke request from gateway
 * This is how the gateway sends commands to nodes
 */
data class NodeInvokeParams(
    @SerializedName("command") val command: String,
    @SerializedName("args") val args: JsonObject? = null
)

// ============================================
// EVENTS (Phone → Gateway)
// ============================================

sealed class NodeEvent {
    abstract fun toProtocolFrame(requestId: String): String
}

/**
 * Generic event wrapper for sending events to gateway
 */
data class EventFrame(
    @SerializedName("type") val type: String = "event",
    @SerializedName("event") val event: String,
    @SerializedName("payload") val payload: Any
) {
    fun toJson(): String = Gson().toJson(this)
}

/**
 * Response frame for node.invoke requests
 */
data class ResponseFrame(
    @SerializedName("type") val type: String = "res",
    @SerializedName("id") val id: String,
    @SerializedName("ok") val ok: Boolean,
    @SerializedName("payload") val payload: Any? = null,
    @SerializedName("error") val error: ErrorPayload? = null
) {
    fun toJson(): String = Gson().toJson(this)
}

data class ErrorPayload(
    @SerializedName("code") val code: String,
    @SerializedName("message") val message: String
)

data class NotificationEvent(
    val id: String,
    val app: String,
    val packageName: String,
    val title: String?,
    val text: String?,
    val actions: List<String> = emptyList(),
    val timestamp: Long = System.currentTimeMillis()
) : NodeEvent() {
    override fun toProtocolFrame(requestId: String): String {
        return EventFrame(
            event = "node.notification",
            payload = mapOf(
                "id" to id,
                "app" to app,
                "package" to packageName,
                "title" to title,
                "text" to text,
                "actions" to actions,
                "timestamp" to timestamp
            )
        ).toJson()
    }
}

data class CallIncomingEvent(
    val callId: String,
    val number: String?,
    val contact: String?,
    val timestamp: Long = System.currentTimeMillis()
) : NodeEvent() {
    override fun toProtocolFrame(requestId: String): String {
        return EventFrame(
            event = "node.call.incoming",
            payload = mapOf(
                "callId" to callId,
                "number" to number,
                "contact" to contact,
                "timestamp" to timestamp
            )
        ).toJson()
    }
}

data class CallEndedEvent(
    val callId: String,
    val durationSeconds: Int,
    val outcome: String, // answered, rejected, missed, voicemail
    val transcript: String? = null
) : NodeEvent() {
    override fun toProtocolFrame(requestId: String): String {
        return EventFrame(
            event = "node.call.ended",
            payload = mapOf(
                "callId" to callId,
                "duration" to durationSeconds,
                "outcome" to outcome,
                "transcript" to transcript
            )
        ).toJson()
    }
}

data class CallAudioEvent(
    val callId: String,
    val transcript: String,
    val isFinal: Boolean = false
) : NodeEvent() {
    override fun toProtocolFrame(requestId: String): String {
        return EventFrame(
            event = "node.call.audio",
            payload = mapOf(
                "callId" to callId,
                "transcript" to transcript,
                "isFinal" to isFinal
            )
        ).toJson()
    }
}

data class ScreenshotEvent(
    val width: Int,
    val height: Int,
    val base64: String
) : NodeEvent() {
    override fun toProtocolFrame(requestId: String): String {
        return EventFrame(
            event = "node.screenshot",
            payload = mapOf(
                "width" to width,
                "height" to height,
                "base64" to base64
            )
        ).toJson()
    }
}

data class StatusEvent(
    val connected: Boolean,
    val batteryPercent: Int,
    val permissions: Map<String, Boolean>
) : NodeEvent() {
    override fun toProtocolFrame(requestId: String): String {
        return EventFrame(
            event = "node.status",
            payload = mapOf(
                "connected" to connected,
                "battery" to batteryPercent,
                "permissions" to permissions
            )
        ).toJson()
    }
}

// ============================================
// COMMANDS (Gateway → Phone via node.invoke)
// ============================================

sealed class NodeCommand {
    abstract val requestId: String?
    
    companion object {
        /**
         * Parse a node.invoke request from the gateway
         */
        fun fromInvoke(requestId: String, params: NodeInvokeParams): NodeCommand? {
            val args = params.args
            return when (params.command) {
                "screenshot" -> ScreenshotCommand(requestId)
                "notification.action" -> NotificationActionCommand(
                    requestId = requestId,
                    notificationId = args?.get("id")?.asString ?: return null,
                    action = args.get("action")?.asString ?: return null,
                    text = args.get("text")?.asString
                )
                "notification.dismiss" -> NotificationDismissCommand(
                    requestId = requestId,
                    notificationId = args?.get("id")?.asString ?: return null
                )
                "call.answer" -> CallAnswerCommand(
                    requestId = requestId,
                    callId = args?.get("callId")?.asString ?: return null,
                    greeting = args.get("greeting")?.asString
                )
                "call.reject" -> CallRejectCommand(
                    requestId = requestId,
                    callId = args?.get("callId")?.asString ?: return null,
                    reason = args.get("reason")?.asString
                )
                "call.silence" -> CallSilenceCommand(
                    requestId = requestId,
                    callId = args?.get("callId")?.asString ?: return null
                )
                "call.speak" -> CallSpeakCommand(
                    requestId = requestId,
                    callId = args?.get("callId")?.asString ?: return null,
                    text = args.get("text")?.asString ?: return null,
                    voice = args.get("voice")?.asString
                )
                "call.hangup" -> CallHangupCommand(
                    requestId = requestId,
                    callId = args?.get("callId")?.asString ?: return null
                )
                else -> null
            }
        }
    }
}

data class ScreenshotCommand(
    override val requestId: String? = null
) : NodeCommand()

data class NotificationActionCommand(
    override val requestId: String? = null,
    val notificationId: String,
    val action: String,
    val text: String? = null // For reply actions
) : NodeCommand()

data class NotificationDismissCommand(
    override val requestId: String? = null,
    val notificationId: String
) : NodeCommand()

data class CallAnswerCommand(
    override val requestId: String? = null,
    val callId: String,
    val greeting: String? = null // TTS greeting to play
) : NodeCommand()

data class CallRejectCommand(
    override val requestId: String? = null,
    val callId: String,
    val reason: String? = null
) : NodeCommand()

data class CallSilenceCommand(
    override val requestId: String? = null,
    val callId: String
) : NodeCommand()

data class CallSpeakCommand(
    override val requestId: String? = null,
    val callId: String,
    val text: String, // Text to speak via TTS
    val voice: String? = null // TTS voice preference
) : NodeCommand()

data class CallHangupCommand(
    override val requestId: String? = null,
    val callId: String
) : NodeCommand()
