package com.sinch.android.rtc.sample.pstn

import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.*
import android.util.Log
import com.sinch.android.rtc.*
import com.sinch.android.rtc.Internals.terminateForcefully
import com.sinch.android.rtc.calling.Call
import com.sinch.android.rtc.sample.pstn.JWT.create
import java.io.IOException

class SinchService : Service() {

    companion object {
        private val TAG = SinchService::class.java.simpleName

        const val APP_KEY = "enter-application-key"
        const val APP_SECRET = "enter-application-secret"
        const val ENVIRONMENT = "ocra.api.sinch.com"
        const val CLI = "enter-cli"

        const val MESSAGE_PERMISSIONS_NEEDED = 1
        const val REQUIRED_PERMISSION = "REQUIRED_PERMISSION"
        const val MESSENGER = "MESSENGER"
        const val CALL_ID = "CALL_ID"
    }

    private val settings: PersistedSettings by lazy {
        PersistedSettings(applicationContext)
    }
    private val sinchServiceInterface: SinchServiceInterface = SinchServiceInterface()

    private var messenger: Messenger? = null
    private var sinchClient: SinchClient? = null
    private var listener: StartFailedListener? = null
    private var userName: String? = null

    override fun onCreate() {
        super.onCreate()
        attemptAutoStart()
    }

    private fun attemptAutoStart() {
        val userName = settings.username
        if (userName.isNotEmpty() && messenger != null) {
            start(userName)
        }
    }

    override fun onDestroy() {
        if (sinchClient?.isStarted == true) {
            sinchClient?.terminateGracefully()
        }
        super.onDestroy()
    }

    private fun start(userName: String) {
        var permissionsGranted = true
        if (sinchClient == null) {
            settings.username = userName
            this.userName = userName
            try {
                createClient(userName)
            } catch (e: IOException) {
                Log.e(TAG, "Failed to create SinchClient ", e)
                return
            }
        }
        try {
            //mandatory checks
            sinchClient?.checkManifest()
        } catch (e: MissingPermissionException) {
            permissionsGranted = false
            if (messenger != null) {
                val message = Message.obtain()
                val bundle = Bundle()
                bundle.putString(REQUIRED_PERMISSION, e.requiredPermission)
                message.data = bundle
                message.what = MESSAGE_PERMISSIONS_NEEDED
                try {
                    messenger!!.send(message)
                } catch (e1: RemoteException) {
                    e1.printStackTrace()
                }
            }
        }
        if (permissionsGranted) {
            Log.d(TAG, "Starting SinchClient")
            sinchClient?.start()
        }
    }

    private fun createClient(username: String) {
        sinchClient = SinchClient.builder()
            .context(applicationContext)
            .userId(username)
            .applicationKey(APP_KEY)
            .environmentHost(ENVIRONMENT).build()
        sinchClient?.addSinchClientListener(MySinchClientListener())
    }

    private fun stop() {
        sinchClient?.terminateGracefully()
        sinchClient = null
    }

    private val isStarted: Boolean
        get() = sinchClient?.isStarted == true

    override fun onBind(intent: Intent): IBinder? {
        messenger = intent.getParcelableExtra(MESSENGER)
        return sinchServiceInterface
    }

    interface StartFailedListener {
        fun onFailed(error: SinchError)
        fun onStarted()
    }

    inner class SinchServiceInterface : Binder() {

        fun callPhoneNumber(phoneNumber: String): Call {
            val sinchClient = sinchClient ?: throw RuntimeException(
                "Trying to make a call when " +
                        "Sinch client is not created"
            )
            return sinchClient.callController.callPhoneNumber(phoneNumber, CLI)
        }

        fun retryStartAfterPermissionGranted() {
            attemptAutoStart()
        }

        val userName get() = this@SinchService.userName

        val isStarted: Boolean
            get() = this@SinchService.isStarted

        fun startClient(userName: String) {
            start(userName)
        }

        fun stopClient() {
            stop()
        }

        fun setStartListener(listener: StartFailedListener?) {
            this@SinchService.listener = listener
        }

        fun getCall(callId: String): Call? {
            return sinchClient?.callController?.getCall(callId)
        }

        val audioController: AudioController?
            get() = sinchClient?.audioController
    }

    private inner class MySinchClientListener : SinchClientListener {

        override fun onClientFailed(client: SinchClient, error: SinchError) {
            listener?.onFailed(error)
            terminateForcefully(sinchClient)
            sinchClient = null
        }

        override fun onClientStarted(client: SinchClient) {
            Log.d(TAG, "SinchClient started")
            listener?.onStarted()
        }

        override fun onLogMessage(level: Int, area: String, message: String) {
            when (level) {
                Log.DEBUG -> Log.d(area, message)
                Log.ERROR -> Log.e(area, message)
                Log.INFO -> Log.i(area, message)
                Log.VERBOSE -> Log.v(area, message)
                Log.WARN -> Log.w(area, message)
                else -> {}
            }
        }

        // The most secure way is to obtain the credentials is from the backend,
        // since storing the Application Secret in the client app is not safe.
        // Following code demonstrates how the JWT that serves as credential should be created,
        // provided the Application Key (APP_KEY), Application Secret (APP_SECRET) and User ID.
        // NB: JWT.create() should run on your backend, and return either valid JWT or signal that
        // user can't be registered.
        // In the first case, register user with Sinch using acuired JWT via clientRegistration
        // .register(...).
        // In the latter - report failure by calling clientRegistration.registerFailed()
        override fun onCredentialsRequired(clientRegistration: ClientRegistration) {
            clientRegistration.register(create(APP_KEY, APP_SECRET, userName.orEmpty()))
        }

        override fun onUserRegistered() {
            Log.d(TAG, "User registered.")
        }

        override fun onUserRegistrationFailed(sinchError: SinchError) {
            Log.e(TAG, "User registration failed: " + sinchError.message)
        }

        override fun onPushTokenRegistered() {
            Log.w(
                TAG,
                "onPushTokenRegistered() should never been called in the application w/o Managed "
                        + "Push support."
            )
        }

        override fun onPushTokenRegistrationFailed(error: SinchError) {
            Log.w(
                TAG,
                "onPushTokenRegistrationFailed() should never been called in the application w/o "
                        + "Managed Push support."
            )
        }

        override fun onPushTokenUnregistered() {
            Log.w(
                TAG, "onPushTokenUnregistered() should never been called in the application w/o "
                        + "Managed Push support."
            )
        }

        override fun onPushTokenUnregistrationFailed(error: SinchError) {
            Log.w(
                TAG,
                "onPushTokenUnregistrationFailed() should never been called in the application "
                        + "w/o Managed Push support."
            )
        }
    }

    private inner class PersistedSettings(context: Context) {

        private val prefKey = "Sinch"
        private val store: SharedPreferences = context.getSharedPreferences(prefKey, MODE_PRIVATE)

        var username: String
            get() = store.getString("Username", "").orEmpty()
            set(username) {
                store.edit()
                    .putString("Username", username)
                    .commit()
            }

    }

}