package guru.core.flutter.analytics.guru_analytics_flutter

import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.FirebaseAnalytics.ConsentStatus
import guru.core.analytics.Constants
import guru.core.analytics.GuruAnalytics
import guru.core.analytics.data.db.model.EventPriority
import guru.core.analytics.data.model.AnalyticsOptions
import guru.core.analytics.handler.AnalyticsCode

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.util.HashMap
import java.util.concurrent.Executor
import java.util.concurrent.Executors

/** GuruAnalyticsFlutterPlugin */
class GuruAnalyticsFlutterPlugin : FlutterPlugin, MethodCallHandler {

    private val executor: Executor = Executors.newFixedThreadPool(1)

    private lateinit var appContext: Context

    private val handler = Handler(Looper.getMainLooper())

    private var appInstanceId: String = ""

    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        appContext = flutterPluginBinding.applicationContext
        channel = MethodChannel(
            flutterPluginBinding.binaryMessenger,
            "flutter.guru.guru_analytics_flutter"
        )
        channel.setMethodCallHandler(this)
    }

    private fun callInitialize(@NonNull call: MethodCall, @NonNull result: Result) {
        val batchLimit = call.argument<Int>(GuruAnalyticsConstants.FieldName.BATCH_LIMIT)
        val uploadPeriodInSeconds =
            call.argument<Int>(GuruAnalyticsConstants.FieldName.UPLOAD_PERIOD_IN_SECONDS)
        val delayedInSeconds =
            call.argument<Int>(GuruAnalyticsConstants.FieldName.DELAYED_IN_SECONDS)
        val eventExpiredInDays =
            call.argument<Int>(GuruAnalyticsConstants.FieldName.EVENT_EXPIRED_IN_DAYS)
        val debug = call.argument<Boolean>(GuruAnalyticsConstants.FieldName.DEBUG) ?: false
        val xAppId = call.argument<String>(GuruAnalyticsConstants.FieldName.X_APP_ID) ?: ""
        val xDeviceInfo =
            call.argument<String>(GuruAnalyticsConstants.FieldName.X_DEVICE_INFO) ?: ""
        Log.w(
            "GuruAnalyticsPlugin",
            "callInitialize: $batchLimit $uploadPeriodInSeconds $delayedInSeconds $eventExpiredInDays $debug"
        )
        GuruAnalytics.Builder(appContext)
            .setBatchLimit(batchLimit)
            .setUploadPeriodInSeconds(uploadPeriodInSeconds?.toLong())
            .setStartUploadDelayInSecond(delayedInSeconds?.toLong())
            .setEventExpiredInDays(eventExpiredInDays)
            .setXAppId(xAppId)
            .setXDeviceInfo(xDeviceInfo)
            .isDebug(debug)
            .isPersistableLog(debug)
            .isInitPeriodicWork(true)
            .setEventHandlerCallback { code, errorInfo -> onAnalyticsCallback(code, errorInfo) }
            .build()

        result.success(true)
    }

    private fun onAnalyticsCallback(code: Int, errorInfo: String?) {
        handler.post {
            channel.invokeMethod(
                "onAnalyticsCallback",
                mapOf(
                    "code" to code,
                    "errorInfo" to errorInfo
                )
            )
        }
    }

    private fun callGetAppInstanceId(@NonNull result: Result) {
        val currentAppInstanceId = appInstanceId
        if (currentAppInstanceId.isNotBlank()) {
            handler.post {
                result.success(currentAppInstanceId)
            }
        } else {
            FirebaseAnalytics.getInstance(appContext).appInstanceId.addOnCompleteListener {
                if (it.isSuccessful) {
                    appInstanceId = it.result ?: ""
                    handler.post {
                        result.success(appInstanceId)
                    }
                } else {
                    handler.post {
                        result.error(
                            GuruAnalyticsConstants.ErrorCode.GET_APP_INSTANCE_ID_FAILED,
                            "getAppInstanceId failed![${it.exception?.toString()}]",
                            "getAppInstanceId failed![${it.exception?.toString()}]"
                        )
                    }
                }
            }
        }
    }

    private fun getConsentStatus(@NonNull call: MethodCall, consentField: String): ConsentStatus {
        val granted = call.argument<Boolean>(consentField) ?: false
        return if (granted) ConsentStatus.GRANTED else ConsentStatus.DENIED
    }

    private fun callSetConsents(@NonNull call: MethodCall, @NonNull result: Result) {
        val adStorageStatus = getConsentStatus(call, ConsentFieldName.AD_STORAGE)
        val analyticsStorageStatus = getConsentStatus(call, ConsentFieldName.ANALYTICS_STORAGE)
        val adPersonalizationStatus = getConsentStatus(call, ConsentFieldName.AD_PERSONALIZATION)
        val adUserDataStatus = getConsentStatus(call, ConsentFieldName.AD_USER_DATA)

        val analytics = FirebaseAnalytics.getInstance(appContext)
        analytics.setConsent(
            mapOf(
                FirebaseAnalytics.ConsentType.AD_STORAGE to adStorageStatus,
                FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE to analyticsStorageStatus,
                FirebaseAnalytics.ConsentType.AD_PERSONALIZATION to adPersonalizationStatus,
                FirebaseAnalytics.ConsentType.AD_USER_DATA to adUserDataStatus
            )
        )
        Log.d(
            "GuruAnalytics",
            "callSetConsents  $adStorageStatus $analyticsStorageStatus $adPersonalizationStatus $adUserDataStatus"
        )
        result.success(true)

    }

    private fun callLogEvent(@NonNull call: MethodCall, @NonNull result: Result) {
        val eventName = call.argument<String>(GuruAnalyticsConstants.FieldName.EVENT_NAME)
        if (eventName == null) {
            result.error(
                GuruAnalyticsConstants.ErrorCode.PROPERTY_ERROR,
                "eventName is Empty",
                "eventName is Empty"
            )
            return
        }
        val parametersMap =
            call.argument<Map<String, Any>>(GuruAnalyticsConstants.FieldName.PARAMETERS)
                ?: emptyMap()
        val parameters: HashMap<String, Any> = HashMap<String, Any>()
        for (parameter in parametersMap) {
            val key = parameter.key
            val value = parameter.value
            if (((value is String)
                        || (value is Long)
                        || (value is Int)
                        || (value is Double)
                        || (value is Float))
            ) {
                parameters[key] = value
            }
        }
        val priority =
            call.argument<Int>(GuruAnalyticsConstants.FieldName.PRIORITY) ?: EventPriority.DEFAULT
        val options = AnalyticsOptions(priority)
        GuruAnalytics.INSTANCE.logEvent(eventName, parameters = parameters, options = options)
        result.success(true)
    }

    private fun callSetUserProperty(@NonNull call: MethodCall, @NonNull result: Result) {
        val propertyName = call.argument<String>(GuruAnalyticsConstants.FieldName.NAME)
        val propertyValue = call.argument<String>(GuruAnalyticsConstants.FieldName.VALUE)
        if (propertyName.isNullOrBlank() || propertyValue.isNullOrBlank()) {
            result.error(
                GuruAnalyticsConstants.ErrorCode.PROPERTY_ERROR,
                "Property Name or Value is NullOrBlank",
                "Property Name or Value is NullOrBlank"
            )
            return
        }
        GuruAnalytics.INSTANCE.setUserProperty(propertyName, propertyValue)
        result.success(true)
    }

    private fun callSetDeviceId(@NonNull call: MethodCall, @NonNull result: Result) {
        val deviceId = call.argument<String>(GuruAnalyticsConstants.FieldName.DEVICE_ID) ?: ""
        GuruAnalytics.INSTANCE.setDeviceId(deviceId)
        result.success(true)
    }

    private fun callSetUid(@NonNull call: MethodCall, @NonNull result: Result) {
        val deviceId = call.argument<String>(GuruAnalyticsConstants.FieldName.USER_ID) ?: ""
        GuruAnalytics.INSTANCE.setUid(deviceId)
        result.success(true)
    }

    private fun callSetAdjustId(@NonNull call: MethodCall, @NonNull result: Result) {
        val adjustId = call.argument<String>(GuruAnalyticsConstants.FieldName.ADJUST_ID) ?: ""
        GuruAnalytics.INSTANCE.setAdjustId(adjustId)
        result.success(true)
    }

    private fun callSetAdId(@NonNull call: MethodCall, @NonNull result: Result) {
        val adId = call.argument<String>(GuruAnalyticsConstants.FieldName.AD_ID) ?: ""
        GuruAnalytics.INSTANCE.setAdId(adId)
        result.success(true)
    }

    private fun callSetFirebaseId(@NonNull call: MethodCall, @NonNull result: Result) {
        val firebaseId = call.argument<String>(GuruAnalyticsConstants.FieldName.FIREBASE_ID) ?: ""
        GuruAnalytics.INSTANCE.setFirebaseId(firebaseId)
        result.success(true)
    }

    private fun callSetScreen(@NonNull call: MethodCall, @NonNull result: Result) {
        val screenName = call.argument<String>(GuruAnalyticsConstants.FieldName.SCREEN) ?: ""
        GuruAnalytics.INSTANCE.setScreen(screenName)
        result.success(true)
    }

    private fun callZipLogs(@NonNull call: MethodCall, @NonNull result: Result) {
        val file = GuruAnalytics.INSTANCE.zipLogs(appContext)
        result.success(file?.absolutePath ?: "")
    }

    private fun callGetStatistic(@NonNull call: MethodCall, @NonNull result: Result) {
        val statistic = GuruAnalytics.INSTANCE.getEventsStatics()
        result.success(
            mapOf<String, Any>(
                GuruAnalyticsConstants.FieldName.LOGGED to statistic.eventCountAll,
                GuruAnalyticsConstants.FieldName.UPLOADED to statistic.eventCountUploaded
            )
        )
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        Log.d("GuruAnalytics", "onMethodCall ${call.method}")
        when (call.method) {
            GuruAnalyticsConstants.Method.Initialize -> callInitialize(call, result)
            GuruAnalyticsConstants.Method.GetAppInstanceId -> callGetAppInstanceId(result)
            GuruAnalyticsConstants.Method.LogEvent -> callLogEvent(call, result)
            GuruAnalyticsConstants.Method.SetUserProperty -> callSetUserProperty(call, result)
            GuruAnalyticsConstants.Method.SetDeviceId -> callSetDeviceId(call, result)
            GuruAnalyticsConstants.Method.SetUid -> callSetUid(call, result)
            GuruAnalyticsConstants.Method.SetAdjustId -> callSetAdjustId(call, result)
            GuruAnalyticsConstants.Method.SetAdId -> callSetAdId(call, result)
            GuruAnalyticsConstants.Method.SetFirebaseId -> callSetFirebaseId(call, result)
            GuruAnalyticsConstants.Method.SetScreen -> callSetScreen(call, result)
            GuruAnalyticsConstants.Method.ZipLogs -> callZipLogs(call, result)
            GuruAnalyticsConstants.Method.GetStatistic -> callGetStatistic(call, result)
            GuruAnalyticsConstants.Method.SetConsents -> callSetConsents(call, result)
            else -> {
                result.notImplemented()
            }
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}
