
package com.humand.demo.modules.CallService

import android.os.Parcelable
import androidx.annotation.Keep
import kotlinx.parcelize.Parcelize

/**
 * Represents a Humand call with all necessary metadata.
 */
@Keep
@Parcelize
data class HumandCall(
    val id: String,
    val providerCallId: String,
    val callerName: String,
    val callerAvatar: String?,
    val cameraEnabled: Boolean,
    val isGroup: Boolean,
    val initializationConfig: String?
) : Parcelable

/**
 * Authentication tokens for API requests.
 */
@Keep
data class AuthTokens(
    val accessToken: String?,
    val refreshToken: String?
)

/**
 * Response from call token API endpoint.
 */
@Keep
data class CallTokenResponse(
    val token: String,
    val participantId: String
)

/**
 * User data from /janus/me endpoint.
 */
@Keep
data class UserResponse(
    val firstName: String?,
    val lastName: String?,
    val fullName: String?
) {
    val displayName: String
        get() {
            if (!fullName.isNullOrBlank()) return fullName
            val parts = listOfNotNull(firstName, lastName).filter { it.isNotBlank() }
            return parts.joinToString(" ")
        }
}

/**
 * Response from /janus/me endpoint.
 */
@Keep
data class GetMeResponse(
    val user: UserResponse
)

/**
 * Response from livestream token API endpoint.
 * participantId is extracted from the JWT token payload.
 */
@Keep
data class LivestreamTokenResponse(
    val token: String
) {
    /**
     * Extract participantId (user_id) from JWT token payload.
     */
    val participantId: String
        get() {
            try {
                val parts = token.split(".")
                if (parts.size >= 2) {
                    val payload = parts[1]
                    // Add padding if needed
                    val paddedPayload = when (payload.length % 4) {
                        2 -> payload + "=="
                        3 -> payload + "="
                        else -> payload
                    }
                    val decodedBytes = android.util.Base64.decode(paddedPayload, android.util.Base64.URL_SAFE)
                    val jsonString = String(decodedBytes, Charsets.UTF_8)
                    val json = org.json.JSONObject(jsonString)
                    return json.optString("user_id", "")
                }
            } catch (e: Exception) {
                // Ignore parsing errors
            }
            return ""
        }
}

/**
 * Participant data to be sent to React Native.
 */
@Keep
data class NativeParticipant(
    val sessionId: String,
    val oduserId: String,
    val name: String,
    val profileImageURL: String?,
    val isSpeaking: Boolean,
    val isDominantSpeaker: Boolean,
    val hasVideo: Boolean,
    val hasAudio: Boolean,
    val isScreenSharing: Boolean,
    val isLocalParticipant: Boolean,
    val connectionQuality: String,
    val source: String
) {
    fun toMap(): Map<String, Any?> = mapOf(
        "sessionId" to sessionId,
        "userId" to oduserId,
        "name" to name,
        "profileImageURL" to profileImageURL,
        "isSpeaking" to isSpeaking,
        "isDominantSpeaker" to isDominantSpeaker,
        "hasVideo" to hasVideo,
        "hasAudio" to hasAudio,
        "isScreenSharing" to isScreenSharing,
        "isLocalParticipant" to isLocalParticipant,
        "connectionQuality" to connectionQuality,
        "source" to source
    )
}

/**
 * Call state data to be sent to React Native.
 */
@Keep
data class NativeCallState(
    val callingState: String,
    val participants: List<NativeParticipant>,
    val localParticipant: NativeParticipant?,
    val microphoneEnabled: Boolean,
    val cameraEnabled: Boolean,
    val speakerEnabled: Boolean
) {
    fun toMap(): Map<String, Any?> = mapOf(
        "callingState" to callingState,
        "participants" to participants.map { it.toMap() },
        "localParticipant" to localParticipant?.toMap(),
        "microphoneEnabled" to microphoneEnabled,
        "cameraEnabled" to cameraEnabled,
        "speakerEnabled" to speakerEnabled
    )
}

fun interface NativeCallsEventListener {
    fun onEvent(eventName: String, properties: Map<String, Any?>)
}

object NativeCallsEventObserver {
    private const val CALL_STARTED = "CALLS_MOBILE_CALL_STARTED"
    private const val CALL_ACCEPTED = "CALLS_MOBILE_CALL_ACCEPTED"
    private const val CALL_REJECTED = "CALLS_MOBILE_CALL_REJECTED"
    private const val CALL_PARTICIPANT_JOINED = "CALLS_MOBILE_CALL_PARTICIPANT_JOINED"
    private const val CALL_ENDED = "CALLS_MOBILE_CALL_ENDED"
    private const val CALL_RINGING = "CALLS_MOBILE_CALL_RINGING"

    private val observers = linkedMapOf<String, NativeCallsEventListener>()
    private val pendingEvents = mutableListOf<Pair<String, Map<String, Any?>>>()

    @Synchronized
    fun addObserver(key: String, observer: NativeCallsEventListener) {
        observers[key] = observer
        val queuedEvents = pendingEvents.toList()
        pendingEvents.clear()
        queuedEvents.forEach { (eventName, properties) ->
            observer.onEvent(eventName, properties)
        }
    }

    @Synchronized
    fun removeObserver(key: String) {
        observers.remove(key)
    }

    fun observeCallStarted(
        callId: String,
        providerCallId: String,
        cameraEnabled: Boolean,
        isGroup: Boolean,
        callee: Any?,
        participantUserIds: List<Int> = emptyList()
    ) {
        emit(
            CALL_STARTED,
            baseProperties(
                callId,
                providerCallId,
                cameraEnabled,
                isGroup,
                "callee",
                callee,
                participantUserIds
            )
        )
    }

    fun observeCallAccepted(
        callId: String,
        providerCallId: String,
        cameraEnabled: Boolean,
        isGroup: Boolean,
        caller: Any?
    ) {
        emit(CALL_ACCEPTED, baseProperties(callId, providerCallId, cameraEnabled, isGroup, "caller", caller))
    }

    fun observeCallRejected(
        callId: String,
        providerCallId: String,
        cameraEnabled: Boolean,
        isGroup: Boolean,
        caller: Any?
    ) {
        emit(CALL_REJECTED, baseProperties(callId, providerCallId, cameraEnabled, isGroup, "caller", caller))
    }

    fun observeParticipantJoined(
        callId: String,
        providerCallId: String,
        cameraEnabled: Boolean,
        isGroup: Boolean,
        participant: Any?
    ) {
        emit(
            CALL_PARTICIPANT_JOINED,
            baseProperties(callId, providerCallId, cameraEnabled, isGroup, "participant", participant)
        )
    }

    fun observeCallEnded(
        callId: String,
        providerCallId: String,
        cameraEnabled: Boolean,
        isGroup: Boolean,
        endSource: String = CallConstants.ObserverEndSource.UNKNOWN,
        endReason: String = CallConstants.ObserverEndReason.UNKNOWN,
        endedByUserId: Any? = null
    ) {
        emit(
            CALL_ENDED,
            baseProperties(
                callId,
                providerCallId,
                cameraEnabled,
                isGroup,
                "ended",
                true,
                extraProperties = mapOf(
                    "endSource" to endSource,
                    "endReason" to endReason,
                    "endedByUserId" to normalizeIdentity(endedByUserId)
                )
            )
        )
    }

    fun observeCallRinging(
        callId: String,
        providerCallId: String,
        cameraEnabled: Boolean,
        isGroup: Boolean,
        direction: String
    ) {
        emit(
            CALL_RINGING,
            baseProperties(
                callId,
                providerCallId,
                cameraEnabled,
                isGroup,
                "ringing",
                true,
                direction = direction
            )
        )
    }

    private fun baseProperties(
        callId: String,
        providerCallId: String,
        cameraEnabled: Boolean,
        isGroup: Boolean,
        identityKey: String,
        identityValue: Any?,
        participantUserIds: List<Int> = emptyList(),
        direction: String? = null,
        extraProperties: Map<String, Any?> = emptyMap()
    ): Map<String, Any?> {
        val properties = linkedMapOf<String, Any?>(
            "id" to callId,
            "providerCallId" to providerCallId,
            "type" to if (cameraEnabled) "videocall" else "audiocall",
            "format" to if (isGroup) "group" else "individual",
            identityKey to normalizeIdentity(identityValue)
        )

        if (participantUserIds.isNotEmpty()) {
            properties["participantUserIds"] = participantUserIds
        }

        direction?.takeIf { it.isNotBlank() }?.let {
            properties["direction"] = it
        }

        extraProperties.forEach { (key, value) ->
            properties[key] = value
        }

        return properties
    }

    private fun normalizeIdentity(value: Any?): Any? = when (value) {
        is String -> value.takeIf { it.isNotBlank() }
        is List<*> -> value.takeIf { it.isNotEmpty() }
        else -> value
    }

    private fun emit(eventName: String, properties: Map<String, Any?>) {
        callLog("[NativeCallsEventObserver] $eventName $properties")
        val currentObservers = synchronized(this) {
            if (observers.isEmpty()) {
                pendingEvents.add(eventName to properties)
            }
            observers.values.toList()
        }
        currentObservers.forEach { observer ->
            observer.onEvent(eventName, properties)
        }
    }
}
