package eu.darken.bluemusic.devices.ui.dashboard

import android.app.Activity
import android.content.Intent
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.darken.bluemusic.bluetooth.core.BluetoothRepo
import eu.darken.bluemusic.bluetooth.core.SourceDevice
import eu.darken.bluemusic.bluetooth.core.speaker.SpeakerDeviceProvider
import eu.darken.bluemusic.common.apps.AppInfo
import eu.darken.bluemusic.common.apps.AppRepo
import eu.darken.bluemusic.common.coroutine.DispatcherProvider
import eu.darken.bluemusic.common.debug.logging.log
import eu.darken.bluemusic.common.debug.logging.logTag
import eu.darken.bluemusic.common.navigation.Nav
import eu.darken.bluemusic.common.navigation.NavigationController
import eu.darken.bluemusic.common.permissions.PermissionHelper
import eu.darken.bluemusic.common.review.ReviewTool
import eu.darken.bluemusic.common.ui.ViewModel4
import eu.darken.bluemusic.common.upgrade.UpgradeRepo
import eu.darken.bluemusic.common.upgrade.isProForUi
import eu.darken.bluemusic.devices.core.DeviceAddr
import eu.darken.bluemusic.devices.core.DeviceLimits
import eu.darken.bluemusic.devices.core.DeviceRepo
import eu.darken.bluemusic.devices.core.DevicesSettings
import eu.darken.bluemusic.devices.core.ManagedDevice
import eu.darken.bluemusic.devices.core.NewDeviceCreator
import eu.darken.bluemusic.devices.core.getDevice
import eu.darken.bluemusic.devices.core.updateVolume
import eu.darken.bluemusic.main.core.GeneralSettings
import eu.darken.bluemusic.monitor.core.audio.AudioStream
import eu.darken.bluemusic.monitor.core.audio.VolumeMode
import eu.darken.bluemusic.monitor.core.audio.VolumeModeTool
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.receiveAsFlow
import javax.inject.Inject

@HiltViewModel
class DashboardViewModel @Inject constructor(
    private val permissionHelper: PermissionHelper,
    private val deviceRepo: DeviceRepo,
    private val volumeModeTool: VolumeModeTool,
    private val upgradeRepo: UpgradeRepo,
    bluetoothSource: BluetoothRepo,
    private val generalSettings: GeneralSettings,
    private val devicesSettings: DevicesSettings,
    private val deviceCreator: NewDeviceCreator,
    private val speakerProvider: SpeakerDeviceProvider,
    private val reviewTool: ReviewTool,
    dispatcherProvider: DispatcherProvider,
    navCtrl: NavigationController,
    appRepo: AppRepo,
) : ViewModel4(dispatcherProvider, logTag("Devices", "Managed", "VM"), navCtrl) {

    private val eventChannel = Channel<DashboardEvent>()
    val events = eventChannel.receiveAsFlow()

    private val devicesFlow = deviceRepo.devices

    private val batteryOptimizationHintFlow = combine(
        flow {
            while (true) {
                emit(System.currentTimeMillis())
                delay(1000)
            }
        },
        generalSettings.isBatteryOptimizationHintDismissed.flow
    ) { _, isDismissed ->
        permissionHelper.getBatteryOptimizationHint(isDismissed)
    }

    private val overlayPermissionHintFlow = combine(
        flow {
            while (true) {
                emit(System.currentTimeMillis())
                delay(1000)
            }
        },
        generalSettings.isAndroid10AppLaunchHintDismissed.flow,
        devicesFlow
    ) { _, isDismissed, devices ->
        val hasDevicesNeedingOverlay = devices.any { it.launchPkgs.isNotEmpty() || it.showHomeScreen || it.keepAwake }
        val hint = permissionHelper.getOverlayPermissionHint(isDismissed, hasDevicesNeedingOverlay)
        hint
    }

    private val notificationPermissionHintFlow = combine(
        flow {
            while (true) {
                emit(System.currentTimeMillis())
                delay(1000)
            }
        },
        generalSettings.isNotificationPermissionHintDismissed.flow
    ) { _, isDismissed ->
        permissionHelper.getNotificationPermissionHint(isDismissed)
    }

    private val dndAccessHintFlow = combine(
        flow {
            while (true) {
                emit(System.currentTimeMillis())
                delay(1000)
            }
        },
        generalSettings.isDndAccessHintDismissed.flow,
        devicesFlow,
    ) { _, isDismissed, devices ->
        val hasDevicesNeedingDnd = devices.any { device ->
            device.getVolume(AudioStream.Type.RINGTONE) != null ||
                device.getVolume(AudioStream.Type.NOTIFICATION) != null ||
                device.dndMode != null
        }
        permissionHelper.getDndAccessHint(isDismissed, hasDevicesNeedingDnd)
    }

    private val devicesWithAppsFlow = combine(
        devicesFlow,
        appRepo.apps
    ) { devices, appInfos ->
        val appInfoMap = appInfos.associateBy { it.packageName }
        devices.map { device ->
            DeviceWithApps(
                device = device,
                launchApps = device.launchPkgs.mapNotNull { pkgName ->
                    appInfoMap[pkgName]
                }
            )
        }
    }

    val state = eu.darken.bluemusic.common.flow.combine(
        upgradeRepo.upgradeInfo,
        bluetoothSource.state,
        devicesWithAppsFlow,
        batteryOptimizationHintFlow,
        overlayPermissionHintFlow,
        notificationPermissionHintFlow,
        dndAccessHintFlow,
        devicesSettings.lockedDevices.flow,
        generalSettings.isSpeakerHintDismissed.flow,
        // The review prompt is a nice-to-have: a failing review backend must never take the whole
        // dashboard down with it, so it falls back to "don't ask".
        reviewTool.state.catch { e ->
            if (e is kotlinx.coroutines.CancellationException) throw e
            emit(ReviewTool.State())
        },
    ) { upgradeInfo, bluetoothState, devicesWithApps, batteryHint, overlayHint, notificationHint, dndHint, lockedDevices, speakerHintDismissed, review ->
        val showSpeakerHint = !speakerHintDismissed &&
            devicesWithApps.any { it.device.type != SourceDevice.Type.PHONE_SPEAKER } &&
            devicesWithApps.none { it.device.type == SourceDevice.Type.PHONE_SPEAKER }
        State(
            isProVersion = upgradeInfo.isPro,
            isBluetoothEnabled = bluetoothState.isEnabled,
            hasBluetoothPermission = bluetoothState.hasPermission,
            devicesWithApps = devicesWithApps,
            lockedDevices = lockedDevices,
            showBatteryOptimizationHint = batteryHint.shouldShow,
            batteryOptimizationIntent = batteryHint.intent,
            showAndroid10AppLaunchHint = overlayHint.shouldShow,
            android10AppLaunchIntent = overlayHint.intent,
            showNotificationPermissionHint = notificationHint.shouldShow,
            showDndAccessHint = dndHint.shouldShow,
            dndAccessIntent = dndHint.intent,
            showSpeakerHint = showSpeakerHint,
            // Lowest priority card: only asked for on an otherwise quiet dashboard, i.e. no hint or
            // permission card is competing for attention and the user actually has devices set up.
            showReviewCard = review.shouldAskForReview &&
                bluetoothState.hasPermission &&
                bluetoothState.isEnabled &&
                !batteryHint.shouldShow &&
                !overlayHint.shouldShow &&
                !dndHint.shouldShow &&
                !notificationHint.shouldShow &&
                !showSpeakerHint &&
                devicesWithApps.isNotEmpty(),
        )
    }.asStateFlow()

    data class DeviceWithApps(
        val device: ManagedDevice,
        val launchApps: List<AppInfo>
    )

    data class State(
        val isProVersion: Boolean = false,
        val isBluetoothEnabled: Boolean = false,
        val hasBluetoothPermission: Boolean = true,
        val devicesWithApps: List<DeviceWithApps> = emptyList(),
        val lockedDevices: Set<DeviceAddr> = emptySet(),
        val isLoading: Boolean = false,
        val showBatteryOptimizationHint: Boolean = false,
        val batteryOptimizationIntent: Intent? = null,
        val showAndroid10AppLaunchHint: Boolean = false,
        val android10AppLaunchIntent: Intent? = null,
        val showNotificationPermissionHint: Boolean = false,
        val showDndAccessHint: Boolean = false,
        val dndAccessIntent: Intent? = null,
        val showSpeakerHint: Boolean = false,
        val showReviewCard: Boolean = false,
    ) {
        // Convenience property for backwards compatibility
        val devices: List<ManagedDevice> get() = devicesWithApps.map { it.device }
    }

    // The upgrade icon is rendered off the passively collected state, which reports non-Pro while
    // billing is still settling. Re-check through the UI gate on tap so an actual Pro user lands on
    // the status view instead of an acquisition screen that immediately navigates back out.
    fun onUpgradeClicked() = launch {
        log(tag) { "onUpgradeClicked()" }
        navTo(Nav.Main.Upgrade(manage = upgradeRepo.isProForUi()))
    }

    // Play's in-app review flow needs the hosting Activity, which never belongs in a [DashboardAction]
    // value. Kept symmetrical with its dismiss counterpart, same shape as [onUpgradeClicked].
    fun reviewNow(activity: Activity) = launch {
        log(tag) { "reviewNow($activity)" }
        reviewTool.reviewNow(activity)
    }

    fun reviewDismiss() = launch {
        log(tag) { "reviewDismiss()" }
        reviewTool.dismiss()
    }

    fun action(action: DashboardAction) = launch {
        log(tag) { "action: $action" }
        when (action) {
            is DashboardAction.RequestBluetoothPermission -> {
                launch {
                    val permission = permissionHelper.getBluetoothPermission()
                    eventChannel.send(DashboardEvent.RequestPermission(permission))
                }
            }

            is DashboardAction.RequestNotificationPermission -> {
                launch {
                    val permission = permissionHelper.getNotificationPermission()
                    if (permission != null) {
                        eventChannel.send(DashboardEvent.RequestPermission(permission))
                    }
                }
            }

            is DashboardAction.DismissBatteryOptimizationHint -> {
                launch {
                    generalSettings.isBatteryOptimizationHintDismissed.update { true }
                }
            }

            is DashboardAction.DismissAndroid10AppLaunchHint -> {
                launch {
                    generalSettings.isAndroid10AppLaunchHintDismissed.update { true }
                }
            }

            is DashboardAction.DismissNotificationPermissionHint -> {
                launch {
                    generalSettings.isNotificationPermissionHintDismissed.update { true }
                }
            }

            is DashboardAction.DismissDndAccessHint -> {
                launch {
                    generalSettings.isDndAccessHintDismissed.update { true }
                }
            }

            is DashboardAction.DismissSpeakerHint -> {
                launch {
                    generalSettings.isSpeakerHintDismissed.update { true }
                }
            }

            is DashboardAction.AddSpeakerDevice -> {
                val devices = deviceRepo.devices.first()
                val speakerAddr = speakerProvider.address

                if (devices.any { it.type == SourceDevice.Type.PHONE_SPEAKER }) {
                    navTo(Nav.Main.DeviceConfig(speakerAddr))
                    return@launch
                }

                if (devices.size >= DeviceLimits.FREE_DEVICE_LIMIT && !upgradeRepo.isProForUi()) {
                    navTo(Nav.Main.Upgrade())
                    return@launch
                }

                deviceCreator.createNewdevice(speakerAddr)
                navTo(Nav.Main.DeviceConfig(speakerAddr))
            }

            is DashboardAction.ToggleAdjustmentLock -> {
                devicesSettings.lockedDevices.update { locked ->
                    if (action.addr in locked) locked - action.addr else locked + action.addr
                }
            }

            is DashboardAction.AdjustVolume -> {
                val locked = devicesSettings.lockedDevices.flow.first()
                if (action.addr in locked) return@launch

                deviceRepo.updateDevice(action.addr) { oldConfig ->
                    oldConfig.updateVolume(action.type, action.volumeMode)
                }

                val device = deviceRepo.getDevice(action.addr)
                if (device?.isActive != true) return@launch

                volumeModeTool.apply(
                    streamId = device.getStreamId(action.type),
                    streamType = action.type,
                    volumeMode = action.volumeMode,
                    visible = device.visibleAdjustments,
                )
            }
        }
    }
}
