package com.ejada.hmg.geofence

import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.location.Location
import androidx.core.content.ContextCompat
import com.ejada.hmg.geofence.intent_receivers.GeofenceBroadcastReceiver
import com.ejada.hmg.geofence.intent_receivers.ReregisterGeofenceJobService
import com.ejada.hmg.utils.*
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofencingClient
import com.google.android.gms.location.GeofencingRequest
import com.google.android.gms.location.LocationServices
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

enum class GeofenceTransition(val value: Int) {
    ENTER(1),
    EXIT(2),
    DWELL(4),

    ENTER_EXIT((ENTER.value or EXIT.value)),
    DWELL_EXIT((DWELL.value or EXIT.value));

    companion object {
        fun fromInt(value: Int) = GeofenceTransition.values().first { it.value == value }
    }

    fun named(): String {
        if (value == 1) return "Enter"
        if (value == 2) return "Exit"
        if (value == 4) return "dWell"
        if (value == (ENTER.value or EXIT.value)) return "Enter or Exit"
        if (value == (DWELL.value or EXIT.value)) return "DWell or Exit"
        return "unknown"
    }
}

class HMG_Geofence {
    // https://developer.android.com/training/location/geofencing#java

    private lateinit var context: Context
    private lateinit var preferences: SharedPreferences
    private val gson = Gson()

    private lateinit var geofencingClient: GeofencingClient
    private val geofencePendingIntent: PendingIntent by lazy {
        val intent = Intent(context, GeofenceBroadcastReceiver::class.java)
        PendingIntent.getBroadcast(
            context,
            0,
            intent,
            PendingIntent.FLAG_IMMUTABLE
        )
    }

    companion object {

        var instance: HMG_Geofence? = null
        fun shared(context: Context): HMG_Geofence {
            if (instance == null) {
                instance = HMG_Geofence()
                instance?.context = context
                instance?.geofencingClient = LocationServices.getGeofencingClient(context)
                instance?.preferences =
                    context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE)
            }
            return instance!!
        }
    }

    private fun limitize(zones: List<GeoZoneModel>): List<GeoZoneModel> {
        var geoZones_ = zones
        if (zones.size > 100)
            geoZones_ = zones.subList(0, 99)
        return geoZones_
    }


    fun register(completion: ((Boolean, java.lang.Exception?) -> Unit)) {
        unRegisterAll { status, exception ->
            val geoZones = getGeoZonesFromPreference(context)
            doRegister(geoZones) { status_, error ->
                completion.let { it(status_, error) }
            }
        }
    }

    fun unRegisterAll(completion: (status: Boolean, exception: Exception?) -> Unit) {
        getActiveGeofences({ success ->
            removeActiveGeofences()
            if (success.isNotEmpty())
                geofencingClient
                    .removeGeofences(success)
                    .addOnSuccessListener {
                        completion(true, null)
                    }
                    .addOnFailureListener {
                        completion(false, it)
                        saveLog(context, "error:REMOVE_GEOFENCES", it.localizedMessage)
                    }
            else
                completion(true, null)

        }, { failed ->
            // Nothing to do with failed geofences.
        })
    }

    private fun doRegister(
        geoZones: List<GeoZoneModel>,
        completion: ((Boolean, java.lang.Exception?) -> Unit)? = null
    ) {
        if (geoZones.isEmpty())
            return

        val geoZones_ = limitize(geoZones)

        fun buildGeofencingRequest(geofences: List<Geofence>): GeofencingRequest {
            return GeofencingRequest.Builder()
                .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL)
                .addGeofences(geofences)
                .build()
        }

        getActiveGeofences({ active ->

            val geofences = mutableListOf<Geofence>()
            geoZones_.forEach {
                it.toGeofence()?.let { geof ->
                    if (!active.contains(geof.requestId)) { // if not already registered then register
                        geofences.add(geof)
                    }
                }
            }

            if (checkPermission() && geofences.isNotEmpty()) {
                geofencingClient
                    .addGeofences(buildGeofencingRequest(geofences), geofencePendingIntent)
                    .addOnSuccessListener {
                        Logs.RegisterGeofence.save(
                            context,
                            "SUCCESS",
                            "Successfuly registered the geofences",
                            Logs.STATUS.SUCCESS
                        )
                        saveActiveGeofence(geofences.map { it.requestId }, listOf())
                        completion?.let { it(true, null) }
                    }
                    .addOnFailureListener { exc ->
                        Logs.RegisterGeofence.save(
                            context,
                            "FAILED_TO_REGISTER",
                            "Failed to register geofence",
                            Logs.STATUS.ERROR
                        )
                        completion?.let { it(false, exc) }
                    }

                // Schedule the job to register after specified duration (due to: events not calling after long period.. days or days [Needs to register fences again])
                HMGUtils.scheduleJob(
                    context,
                    ReregisterGeofenceJobService::class.java,
                    ReregisterGeofenceJobService.JobID,
                    ReregisterGeofenceJobService.TriggerIntervalDuration
                )
            }

        }, null)

    }

    fun getGeoZonesFromPreference(context: Context): List<GeoZoneModel> {
        val pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE)
        val json = pref.getString(PREF_KEY_HMG_ZONES, "[]")

        val geoZones = GeoZoneModel().listFrom(json!!)
        return geoZones
    }

    fun saveActiveGeofence(success: List<String>, failed: List<String>) {
        val jsonSuccess = gson.toJson(success)
        val jsonFailure = gson.toJson(failed)
        preferences.edit().putString(PREF_KEY_SUCCESS, jsonSuccess).apply()
        preferences.edit().putString(PREF_KEY_FAILED, jsonFailure).apply()
    }

    fun removeActiveGeofences() {
        preferences.edit().putString(PREF_KEY_SUCCESS, "[]").apply()
        preferences.edit().putString(PREF_KEY_FAILED, "[]").apply()
    }

    fun getActiveGeofences(
        success: (success: List<String>) -> Unit,
        failure: ((failed: List<String>) -> Unit)?
    ) {
        val type = object : TypeToken<List<String?>?>() {}.type

        val jsonSuccess = preferences.getString(PREF_KEY_SUCCESS, "[]")
        val success = gson.fromJson<List<String>>(jsonSuccess, type)
        success(success)

        if (failure != null) {
            val jsonFailure = preferences.getString(PREF_KEY_FAILED, "[]")
            val failed = gson.fromJson<List<String>>(jsonFailure, type)
            failure(failed)
        }

    }

    private fun checkPermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    }

    fun getPatientID(): Int? {
        var profileJson = preferences.getString("flutter.imei-user-data", null)
        if (profileJson == null)
            profileJson = preferences.getString("flutter.user-profile", null)

        val type = object : TypeToken<Map<String?, Any?>?>() {}.type
        return gson.fromJson<Map<String?, Any?>?>(profileJson, type)
            ?.get("PatientID")
            .toString()
            .toDoubleOrNull()
            ?.toInt()
    }


    fun handleEvent(
        triggerGeofences: List<Geofence>,
        location: Location,
        transition: GeofenceTransition
    ) {
        getPatientID()?.let { patientId ->
            getActiveGeofences({ activeGeofences ->

                triggerGeofences.forEach { geofence ->
                    // Extract PointID from 'geofence.requestId' and find from active geofences
                    val pointID =
                        activeGeofences.firstOrNull { it == geofence.requestId }?.split('_')
                            ?.first()
                    if (!pointID.isNullOrEmpty() && pointID.toIntOrNull() != null) {

                        val body = mutableMapOf<String, Any?>(
                            "PointsID" to pointID.toIntOrNull(),
                            "GeoType" to transition.value,
                            "PatientID" to patientId
                        )
                        body.putAll(HMGUtils.defaultHTTPParams(context))

                        httpPost<Map<String, Any>>(API.LOG_GEOFENCE, body, { response ->
                            saveLog(
                                context,
                                "HMG_GEOFENCE_NOTIFY",
                                "Success: Notified to server\uD83D\uDE0E."
                            )
                            sendNotification(
                                context,
                                transition.named(),
                                geofence.requestId,
                                "Notified to server.😎"
                            )
                        }, { exception ->
                            val errorMessage = "${transition.named()}, ${geofence.requestId}"
                            saveLog(
                                context,
                                "HMG_GEOFENCE_NOTIFY",
                                "failed: $errorMessage | error: ${exception.localizedMessage}"
                            )
                            sendNotification(
                                context,
                                transition.named(),
                                geofence.requestId,
                                "Failed to notify server😔 -> ${exception.localizedMessage}"
                            )
                        })

                    }
                }

            }, null)
        }
    }
}