package com.idea.gametime.health

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.PermissionController
import androidx.lifecycle.Lifecycle
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.records.DistanceRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
import androidx.health.connect.client.records.metadata.Metadata
import androidx.health.connect.client.units.Energy
import androidx.health.connect.client.units.Length
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.lang.ref.WeakReference
import java.time.Instant
import java.time.ZoneOffset
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

object HealthConnectPlugin {
    private const val METHOD_CHANNEL = "gametime.health_connect/methods"
    private const val LOG_TAG = "GTHealthConnect"
    private const val HEALTH_CONNECT_PROVIDER_PACKAGE = "com.google.android.apps.healthdata"
    private const val PLAY_STORE_PACKAGE = "com.android.vending"
    private const val FALLBACK_OPEN_DELAY_MS = 1200L

    private val requiredPermissions =
        setOf(
            HealthPermission.getWritePermission(ExerciseSessionRecord::class),
            HealthPermission.getWritePermission(DistanceRecord::class),
            HealthPermission.getWritePermission(TotalCaloriesBurnedRecord::class),
            HealthPermission.getWritePermission(HeartRateRecord::class),
        )

    private var appContext: Context? = null
    private var activityRef: WeakReference<Activity>? = null
    private var permissionRequestLauncher: ActivityResultLauncher<Set<String>>? = null
    private var pendingPermissionResult: MethodChannel.Result? = null
    private var fallbackOpenRunnable: Runnable? = null
    private val mainHandler = Handler(Looper.getMainLooper())
    private val mainScope = CoroutineScope(Dispatchers.Main)

    fun prepare(activity: Activity) {
        appContext = activity.applicationContext
        activityRef = WeakReference(activity)
        registerPermissionRequestLauncher(activity)
    }

    fun register(flutterEngine: FlutterEngine, activity: Activity) {
        appContext = activity.applicationContext
        activityRef = WeakReference(activity)
        if (permissionRequestLauncher == null) {
            registerPermissionRequestLauncher(activity)
        }
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL)
            .setMethodCallHandler(::handleMethodCall)
    }

    private fun registerPermissionRequestLauncher(activity: Activity) {
        Log.d(LOG_TAG, "registerPermissionRequestLauncher(activity=${activity::class.java.name})")
        val componentActivity = activity as? ComponentActivity
        if (componentActivity == null) {
            Log.e(LOG_TAG, "Activity is not a ComponentActivity; permission launcher unavailable.")
            return
        }
        try {
            val context = activity.applicationContext
            permissionRequestLauncher?.unregister()
            permissionRequestLauncher =
                componentActivity.activityResultRegistry.register(
                    "gametime_health_connect_permissions",
                    PermissionController.createRequestPermissionResultContract(),
                ) { grantedPermissions ->
                    Log.d(
                        LOG_TAG,
                        "permission request callback(granted=${grantedPermissions.size})",
                    )
                    cancelFallbackOpen()
                    val result = pendingPermissionResult ?: return@register
                    pendingPermissionResult = null
                    mainScope.launch {
                        val state = try {
                            connectionStateMap(
                                context,
                                grantedPermissions,
                                permissionRequestOutcome =
                                    if (grantedPermissions.containsAll(requiredPermissions)) {
                                        "granted"
                                    } else {
                                        "denied"
                                    },
                            )
                        } catch (error: Throwable) {
                            Log.e(LOG_TAG, "connection state failed after callback.", error)
                            result.error("health_connect_state_failed", error.message, null)
                            return@launch
                        }
                        result.success(state)
                    }
                }
            Log.d(LOG_TAG, "permission launcher registered.")
        } catch (error: Throwable) {
            Log.e(LOG_TAG, "permission launcher registration failed.", error)
            permissionRequestLauncher = null
        }
    }

    private fun handleMethodCall(call: MethodCall, result: MethodChannel.Result) {
        val context = appContext
        if (context == null) {
            result.error("health_connect_unavailable", "Application context unavailable.", null)
            return
        }
        when (call.method) {
            "connectionState" -> connectionState(context, result)
            "requestPermissions" -> requestPermissions(context, result)
            "openSettings" -> openSettings(context, result)
            "exportWorkout" -> exportWorkout(context, call.arguments, result)
            else -> result.notImplemented()
        }
    }

    private fun connectionState(context: Context, result: MethodChannel.Result) {
        mainScope.launch {
            try {
                result.success(connectionStateMap(context))
            } catch (error: Throwable) {
                result.error("health_connect_state_failed", error.message, null)
            }
        }
    }

    private suspend fun connectionStateMap(
        context: Context,
        grantedOverride: Set<String>? = null,
        permissionRequestOutcome: String? = null,
    ): Map<String, Any> {
        var availability = availability(context)
        val grantedPermissions = if (availability == "available") {
            try {
                grantedOverride ?: HealthConnectClient.getOrCreate(context)
                    .permissionController
                    .getGrantedPermissions()
            } catch (_: SecurityException) {
                availability = "accessBlocked"
                emptySet()
            } catch (_: IllegalStateException) {
                availability = "accessBlocked"
                emptySet()
            }
        } else {
            emptySet()
        }
        val state = mutableMapOf<String, Any>(
            "availability" to availability,
            "requiredPermissions" to requiredPermissions.toList(),
            "grantedPermissions" to grantedPermissions.toList(),
        )
        if (permissionRequestOutcome != null) {
            state["permissionRequestOutcome"] = permissionRequestOutcome
        }
        return state
    }

    private fun availability(context: Context): String {
        return when (HealthConnectClient.getSdkStatus(context)) {
            HealthConnectClient.SDK_AVAILABLE -> "available"
            HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED ->
                "providerUpdateRequired"
            else -> "unavailable"
        }
    }

    private fun requestPermissions(context: Context, result: MethodChannel.Result) {
        val availability = availability(context)
        Log.d(LOG_TAG, "requestPermissions availability=$availability")
        if (availability != "available") {
            mainScope.launch {
                result.success(connectionStateMap(context))
            }
            return
        }
        val activity = activityRef?.get()
        if (activity == null) {
            Log.e(LOG_TAG, "requestPermissions failed: activity unavailable.")
            result.error("health_connect_unavailable", "Activity unavailable.", null)
            return
        }
        if (pendingPermissionResult != null) {
            Log.e(LOG_TAG, "requestPermissions failed: request already active.")
            result.error(
                "health_connect_request_in_progress",
                "Permission request already active.",
                null,
            )
            return
        }
        pendingPermissionResult = result
        val launcher = permissionRequestLauncher
        if (launcher == null) {
            Log.e(LOG_TAG, "requestPermissions fallback: launcher unavailable.")
            openSettingsFallback(context, activity, "settingsFallback")
            return
        }
        try {
            Log.d(LOG_TAG, "launching Health Connect permission contract.")
            launcher.launch(requiredPermissions)
            Log.d(LOG_TAG, "Health Connect permission contract launch returned.")
            scheduleFallbackOpen(context, activity)
        } catch (error: Throwable) {
            Log.e(LOG_TAG, "Health Connect permission contract launch failed.", error)
            openSettingsFallback(context, activity, "launchFailed")
        }
    }

    private fun scheduleFallbackOpen(context: Context, activity: Activity) {
        cancelFallbackOpen()
        val runnable = Runnable {
            if (pendingPermissionResult == null) {
                return@Runnable
            }
            val componentActivity = activity as? ComponentActivity
            if (componentActivity != null &&
                !componentActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
            ) {
                Log.d(LOG_TAG, "permission UI appears to have focus; fallback not opened.")
                return@Runnable
            }
            Log.d(LOG_TAG, "permission UI did not take focus; opening Health Connect settings fallback.")
            openSettingsFallback(context, activity, "settingsFallback")
        }
        fallbackOpenRunnable = runnable
        mainHandler.postDelayed(runnable, FALLBACK_OPEN_DELAY_MS)
    }

    private fun cancelFallbackOpen() {
        fallbackOpenRunnable?.let(mainHandler::removeCallbacks)
        fallbackOpenRunnable = null
    }

    private fun openSettingsFallback(context: Context, activity: Activity, outcome: String) {
        cancelFallbackOpen()
        val pendingResult = pendingPermissionResult
        pendingPermissionResult = null
        try {
            activity.startActivity(settingsIntent())
            Log.d(LOG_TAG, "Health Connect settings fallback opened(outcome=$outcome).")
            mainScope.launch {
                try {
                    pendingResult?.success(
                        connectionStateMap(
                            context,
                            permissionRequestOutcome = outcome,
                        ),
                    )
                } catch (stateError: Throwable) {
                    Log.e(LOG_TAG, "connection state failed after settings fallback.", stateError)
                    pendingResult?.error("health_connect_state_failed", stateError.message, null)
                }
            }
        } catch (error: Throwable) {
            Log.e(LOG_TAG, "Health Connect settings fallback failed.", error)
            pendingResult?.error("health_connect_permission_request_failed", error.message, null)
        }
    }

    private fun openSettings(context: Context, result: MethodChannel.Result) {
        val activity = activityRef?.get()
        if (activity == null) {
            result.success(false)
            return
        }
        val intents =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                listOf(settingsIntent())
            } else if (availability(context) == "providerUpdateRequired") {
                listOf(providerUpdateIntent(context), settingsIntent())
            } else {
                listOf(settingsIntent())
            }
        val opened = intents.any { intent ->
            try {
                Log.d(LOG_TAG, "openSettings launching ${intent.action}.")
                activity.startActivity(intent)
                Log.d(LOG_TAG, "openSettings launch ok.")
                true
            } catch (error: ActivityNotFoundException) {
                Log.e(LOG_TAG, "openSettings activity not found.", error)
                false
            } catch (error: SecurityException) {
                Log.e(LOG_TAG, "openSettings security failure.", error)
                false
            }
        }
        result.success(opened)
    }

    private fun providerUpdateIntent(context: Context): Intent {
        val uri =
            "market://details?id=$HEALTH_CONNECT_PROVIDER_PACKAGE&url=healthconnect%3A%2F%2Fonboarding"
        return Intent(Intent.ACTION_VIEW).apply {
            setPackage(PLAY_STORE_PACKAGE)
            data = Uri.parse(uri)
            putExtra("overlay", true)
            putExtra("callerId", context.packageName)
        }
    }

    private fun settingsIntent(): Intent =
        Intent(HealthConnectClient.ACTION_HEALTH_CONNECT_SETTINGS)

    private fun exportWorkout(
        context: Context,
        arguments: Any?,
        result: MethodChannel.Result,
    ) {
        val map = arguments as? Map<*, *>
        if (map == null) {
            result.error("invalid_health_connect_workout", "Workout payload must be a map.", null)
            return
        }
        mainScope.launch {
            try {
                if (availability(context) != "available") {
                    result.error("health_connect_unavailable", "Health Connect is unavailable.", null)
                    return@launch
                }
                val grantedPermissions =
                    HealthConnectClient.getOrCreate(context)
                        .permissionController
                        .getGrantedPermissions()
                if (!grantedPermissions.containsAll(requiredPermissions)) {
                    result.error(
                        "health_connect_permissions_required",
                        "Health Connect write permissions are not granted.",
                        null,
                    )
                    return@launch
                }
                val records = recordsFromWorkoutPayload(map)
                HealthConnectClient.getOrCreate(context).insertRecords(records)
                result.success(null)
            } catch (error: Throwable) {
                result.error("health_connect_export_failed", error.message, null)
            }
        }
    }

    private fun recordsFromWorkoutPayload(map: Map<*, *>): List<Record> {
        val historyId = nonBlankString(map["historyId"], "historyId")
        val title = map["title"] as? String ?: "GameTime Basketball"
        val start = epochMs(map["startedAtEpochMs"], "startedAtEpochMs")
        val end = epochMs(map["endedAtEpochMs"], "endedAtEpochMs")
        if (!end.isAfter(start)) {
            throw IllegalArgumentException("Workout end time must be after start time.")
        }
        val records = mutableListOf<Record>(
            ExerciseSessionRecord(
                startTime = start,
                startZoneOffset = ZoneOffset.UTC,
                endTime = end,
                endZoneOffset = ZoneOffset.UTC,
                exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BASKETBALL,
                title = title,
                notes = "Exported from GameTime.",
                metadata = Metadata.manualEntry(
                    clientRecordId = clientRecordId(historyId, "exercise-session"),
                ),
            ),
        )
        val distanceMeters = positiveDouble(map["totalDistanceMeters"])
        if (distanceMeters != null) {
            records += DistanceRecord(
                startTime = start,
                startZoneOffset = ZoneOffset.UTC,
                endTime = end,
                endZoneOffset = ZoneOffset.UTC,
                distance = Length.meters(distanceMeters),
                metadata = Metadata.manualEntry(
                    clientRecordId = clientRecordId(historyId, "distance"),
                ),
            )
        }
        val caloriesKcal = positiveDouble(map["totalCaloriesKcal"])
        if (caloriesKcal != null) {
            records += TotalCaloriesBurnedRecord(
                startTime = start,
                startZoneOffset = ZoneOffset.UTC,
                endTime = end,
                endZoneOffset = ZoneOffset.UTC,
                energy = Energy.kilocalories(caloriesKcal),
                metadata = Metadata.manualEntry(
                    clientRecordId = clientRecordId(historyId, "calories"),
                ),
            )
        }
        val heartRateSamples = heartRateSamples(map["heartRateSamples"], start, end)
        if (heartRateSamples.isNotEmpty()) {
            records += HeartRateRecord(
                startTime = start,
                startZoneOffset = ZoneOffset.UTC,
                endTime = end,
                endZoneOffset = ZoneOffset.UTC,
                samples = heartRateSamples,
                metadata = Metadata.manualEntry(
                    clientRecordId = clientRecordId(historyId, "heart-rate"),
                ),
            )
        }
        return records
    }

    private fun nonBlankString(value: Any?, fieldName: String): String {
        val text = (value as? String)?.trim()
        if (text.isNullOrEmpty()) {
            throw IllegalArgumentException("$fieldName must be a non-blank string.")
        }
        return text
    }

    private fun clientRecordId(historyId: String, kind: String): String =
        "gametime:$historyId:$kind"

    private fun heartRateSamples(
        payload: Any?,
        start: Instant,
        end: Instant,
    ): List<HeartRateRecord.Sample> {
        val items = payload as? List<*> ?: return emptyList()
        return items.mapNotNull { item ->
            val map = item as? Map<*, *> ?: return@mapNotNull null
            val time =
                (map["timeEpochMs"] as? Number)?.toLong()?.let(Instant::ofEpochMilli)
                    ?: return@mapNotNull null
            val beatsPerMinute = (map["beatsPerMinute"] as? Number)?.toLong()
                ?: return@mapNotNull null
            if (beatsPerMinute <= 0 || time.isBefore(start) || time.isAfter(end)) {
                return@mapNotNull null
            }
            HeartRateRecord.Sample(time = time, beatsPerMinute = beatsPerMinute)
        }
    }

    private fun epochMs(value: Any?, fieldName: String): Instant {
        val epochMs = (value as? Number)?.toLong()
            ?: throw IllegalArgumentException("$fieldName must be an epoch millisecond number.")
        return Instant.ofEpochMilli(epochMs)
    }

    private fun positiveDouble(value: Any?): Double? {
        val number = (value as? Number)?.toDouble() ?: return null
        return if (number > 0.0) number else null
    }
}
