package com.sinch.android.rtc.sample.push.fcm

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.IBinder
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.sinch.android.rtc.SinchPush.isSinchPushPayload
import com.sinch.android.rtc.SinchPush.queryPushNotificationPayload
import com.sinch.android.rtc.calling.CallNotificationResult
import com.sinch.android.rtc.sample.push.FcmTokenPersistence.setFcmRegistrationToken
import com.sinch.android.rtc.sample.push.SinchService

class FcmListenerService : FirebaseMessagingService() {

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        val data = remoteMessage.data
        Log.d(TAG, "Got FCM message.")
        if (!isSinchPushPayload(data)) {
            Log.d(TAG, "Non Sinch push payload received. Ignoring.")
            return
        }
        val result = try {
            queryPushNotificationPayload(applicationContext, data)
        } catch (e: Exception) {
            Log.e(TAG, "Error while executing queryPushNotificationPayload", e)
            return
        }

        // Optional: inspect the payload w/o starting Sinch Client and thus avoiding
        // onIncomingCall()
        // e.g. useful to fetch user related polices (blacklist), resources (to show a picture,
        // etc).
        Log.d(TAG, "queryPushNotificationPayload() -> display name: " + result.displayName)
        Log.d(TAG, "queryPushNotificationPayload() -> call headers: " + result.callHeaders)
        Log.d(TAG, "queryPushNotificationPayload() -> remote user ID: " + result.remoteUserId)

        // Mandatory: forward payload to the SinchClient.
        object : ServiceConnection {
            private var callNotificationResult: CallNotificationResult? = null

            override fun onServiceConnected(name: ComponentName, service: IBinder) {
                callNotificationResult?.let {
                    val sinchService = service as SinchService.SinchServiceInterface
                    try {
                        sinchService.relayRemotePushNotificationPayload(it)
                        // Optional: handle call notification result data, e.g. show a system
                        // notification or similar.
                    } catch (e: Exception) {
                        Log.e(TAG, "Error while executing relayRemotePushNotificationPayload", e)
                    }
                }
                callNotificationResult = null
            }

            override fun onServiceDisconnected(name: ComponentName) {}

            fun relayCallNotification(callNotificationResult: CallNotificationResult) {
                this.callNotificationResult = callNotificationResult
                createNotificationChannel(NotificationManager.IMPORTANCE_MAX)
                applicationContext.bindService(
                    Intent(applicationContext, SinchService::class.java), this,
                    BIND_AUTO_CREATE
                )
            }
        }.relayCallNotification(result)
    }

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        Log.d(TAG, "onNewToken called with registration token: $token")
        setFcmRegistrationToken(this, token)
        restartSinchClientIfNeeded()
    }

    private fun createNotificationChannel(importance: Int) {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name: CharSequence = "Sinch"
            val channel = NotificationChannel(CALL_PUSH_CHANNEL_ID, name, importance).apply {
                description = "Incoming Sinch Push Notifications."
            }
            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            val notificationManager = getSystemService(
                NotificationManager::class.java
            )
            notificationManager.createNotificationChannel(channel)
        }
    }

    private fun restartSinchClientIfNeeded() {
        object : ServiceConnection {
            override fun onServiceConnected(componentName: ComponentName, service: IBinder) {
                val sinchService = service as SinchService.SinchServiceInterface
                if (sinchService.username.isEmpty()) {
                    Log.d(TAG,"onNewToken callback received before user logged in, ignoring.")
                    // Callback received before user logged in, ignoring.
                    return
                }
                sinchService.apply {
                    unregisterPushToken()
                    stopClient()
                    startClient()
                }
            }

            override fun onServiceDisconnected(componentName: ComponentName) {}

            fun update() {
                applicationContext.bindService(
                    Intent(applicationContext, SinchService::class.java), this,
                    BIND_AUTO_CREATE
                )
            }
        }.update()
    }

    companion object {
        private val TAG = FcmListenerService::class.java.simpleName
        const val CALL_PUSH_CHANNEL_ID = "Sinch Push Notification Channel"
        const val CALL_NOTIFICATION_ID = 14
    }
}
