package com.jttools.datacontrol.service

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import com.jttools.datacontrol.MainActivity
import com.jttools.datacontrol.R
import com.jttools.datacontrol.data.AppSettings
import com.jttools.datacontrol.data.DataState
import com.jttools.datacontrol.data.SettingsRepository
import com.jttools.datacontrol.data.UsageRepository
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.time.LocalDate
import javax.inject.Inject
import kotlin.math.max

/**
 * Service de premier plan qui surveille en continu la consommation de données mobiles,
 * pilote le pare-feu [FirewallVpnService] et met à jour la notification permanente.
 *
 * Règles appliquées (voir README) :
 *  - Sur Wi-Fi : aucun filtrage par défaut (le pare-feu est arrêté), sauf si le filtrage Wi-Fi
 *    est explicitement activé (Liste blanche Wi-Fi).
 *  - Sur données mobiles : liste blanche active en permanence (seules les apps cochées ont accès),
 *    ET une coupure totale cyclique (même liste blanche) se déclenche automatiquement toutes les
 *    X minutes pendant une courte pulsation, que le forfait soit dépassé ou non.
 */
@AndroidEntryPoint
class DataMonitorService : LifecycleService() {

    @Inject lateinit var settingsRepository: SettingsRepository
    @Inject lateinit var usageRepository: UsageRepository
    @Inject lateinit var connectivityObserver: ConnectivityObserver
    @Inject lateinit var stateHolder: MonitorStateHolder

    private var latestSettings: AppSettings = AppSettings()
    private var latestTransport: NetworkTransport = NetworkTransport.NONE
    private var vpnRevoked = false

    private var lastAppliedMode: VpnMode? = null
    private var lastAppliedWhitelist: Set<String> = emptySet()

    private var cutoffCycleAnchorMillis: Long? = null
    private var inPulse = false
    private var pulseStartMillis: Long = 0L
    private var settingsLoaded = false

    private val vpnRevokedReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            vpnRevoked = true
            lifecycleScope.launch { settingsRepository.setControlEnabled(false) }
        }
    }

    private enum class VpnMode { FILTER, BLOCK_ALL, STOPPED }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        // START_STICKY : si le système tue ce service (mémoire faible), il le relance
        // automatiquement (avec un Intent null) dès que les ressources redeviennent disponibles.
        return START_STICKY
    }

    override fun onCreate() {
        super.onCreate()
        ContextCompat.registerReceiver(
            this, vpnRevokedReceiver, IntentFilter(FirewallVpnService.ACTION_VPN_REVOKED),
            ContextCompat.RECEIVER_NOT_EXPORTED
        )
        startForeground(NOTIF_ID, buildNotification(stateHolder.snapshot.value))

        lifecycleScope.launch {
            settingsRepository.settingsFlow.collect {
                // Ne recopier l'état persisté du minuteur de coupure qu'à la toute première valeur
                // réelle lue en base — pas question d'écraser avec les valeurs par défaut si un
                // tick() déclenché par une autre coroutine s'exécute avant cette première émission.
                if (!settingsLoaded) {
                    cutoffCycleAnchorMillis = it.cutoffCycleAnchorMillis
                    inPulse = it.cutoffInPulse
                    pulseStartMillis = it.cutoffPulseStartMillis
                    settingsLoaded = true
                }
                latestSettings = it
                if (!it.controlEnabled) vpnRevoked = false
                tick()
            }
        }
        lifecycleScope.launch {
            connectivityObserver.observe().collect {
                latestTransport = it
                tick()
            }
        }
        lifecycleScope.launch {
            while (isActive) {
                tick()
                kotlinx.coroutines.delay(TICK_INTERVAL_MS)
            }
        }
    }

    private fun tick() {
        val settings = latestSettings

        // Le calcul de la consommation doit rester à jour même quand le contrôle est désactivé
        // (ex: pour refléter immédiatement une valeur définie manuellement dans Réglages), il ne
        // doit donc pas dépendre de settings.controlEnabled.
        val consumedBytes = usageRepository.consumedBytesSince(settings.baselineMobileBytesAtReset)
        if (usageRepository.hasCounterReset(settings.baselineMobileBytesAtReset)) {
            lifecycleScope.launch { settingsRepository.rebaseline(usageRepository.totalMobileBytes()) }
        }
        val consumedMo = UsageRepository.bytesToMo(consumedBytes)
        val overageMo = max(0L, consumedMo - settings.planLimitMo)
        val estimatedTotal = settings.planPriceEuro + overageMo * settings.overagePricePerMoEuro
        val percent = if (settings.planLimitMo > 0) consumedMo.toFloat() / settings.planLimitMo else 0f

        if (!settings.controlEnabled) {
            applyVpnMode(VpnMode.STOPPED, emptySet())
            stateHolder.update {
                it.copy(
                    consumedMo = consumedMo,
                    planLimitMo = settings.planLimitMo,
                    overageMo = overageMo,
                    estimatedTotalPriceEuro = estimatedTotal,
                    dataState = if (percent >= WARNING_THRESHOLD) DataState.WARNING else DataState.OK,
                    nextAutoResetDate = settings.nextAutoResetDate(),
                    serviceRunning = false
                )
            }
            updateNotification(stateHolder.snapshot.value)
            return
        }

        maybeAutoReset(settings)

        val dataState: DataState
        var nextCutoffAt: Long? = null

        when {
            latestTransport == NetworkTransport.WIFI -> {
                if (settings.wifiFilterEnabled && FirewallVpnService.isPrepared(this)) {
                    applyVpnMode(VpnMode.FILTER, settings.whitelistedPackagesWifi)
                } else {
                    applyVpnMode(VpnMode.STOPPED, emptySet())
                }
                resetCutoffCycle()
                dataState = DataState.WIFI
            }
            latestTransport == NetworkTransport.CELLULAR && !vpnRevoked && FirewallVpnService.isPrepared(this) -> {
                // La coupure cyclique tourne en continu sur données mobiles, indépendamment du
                // dépassement du forfait : elle ne doit pas attendre le hors-forfait pour se déclencher.
                val now = System.currentTimeMillis()
                val intervalMs = settings.cutoffIntervalMinutes * 60_000L
                if (cutoffCycleAnchorMillis == null) {
                    cutoffCycleAnchorMillis = now
                    persistCutoffState()
                }
                val anchor = cutoffCycleAnchorMillis!!

                if (inPulse) {
                    if (now - pulseStartMillis >= PULSE_DURATION_MS) {
                        inPulse = false
                        cutoffCycleAnchorMillis = now
                        persistCutoffState()
                        applyVpnMode(VpnMode.FILTER, settings.whitelistedPackages)
                    } else {
                        applyVpnMode(VpnMode.BLOCK_ALL, emptySet())
                    }
                } else if (now - anchor >= intervalMs) {
                    inPulse = true
                    pulseStartMillis = now
                    persistCutoffState()
                    applyVpnMode(VpnMode.BLOCK_ALL, emptySet())
                } else {
                    applyVpnMode(VpnMode.FILTER, settings.whitelistedPackages)
                }
                nextCutoffAt = if (inPulse) pulseStartMillis + PULSE_DURATION_MS else anchor + intervalMs
                dataState = if (inPulse) DataState.CUT else if (percent >= WARNING_THRESHOLD) DataState.WARNING else DataState.OK
            }
            else -> {
                // Pas de réseau, transport inconnu, ou VPN non préparé/révoqué : rien à filtrer.
                applyVpnMode(VpnMode.STOPPED, emptySet())
                resetCutoffCycle()
                dataState = if (percent >= WARNING_THRESHOLD) DataState.WARNING else DataState.OK
            }
        }

        stateHolder.update {
            it.copy(
                consumedMo = consumedMo,
                planLimitMo = settings.planLimitMo,
                overageMo = overageMo,
                estimatedTotalPriceEuro = estimatedTotal,
                dataState = dataState,
                nextAutoResetDate = settings.nextAutoResetDate(),
                nextCutoffAtMillis = nextCutoffAt,
                serviceRunning = true
            )
        }
        updateNotification(stateHolder.snapshot.value)
    }

    private fun resetCutoffCycle() {
        if (cutoffCycleAnchorMillis == null && !inPulse) return
        cutoffCycleAnchorMillis = null
        inPulse = false
        persistCutoffState()
    }

    private fun persistCutoffState() {
        val anchor = cutoffCycleAnchorMillis
        val pulse = inPulse
        val pulseStart = pulseStartMillis
        lifecycleScope.launch { settingsRepository.setCutoffCycleState(anchor, pulse, pulseStart) }
    }

    private fun maybeAutoReset(settings: AppSettings) {
        if (settings.autoResetEnabled && !LocalDate.now().isBefore(settings.nextAutoResetDate())) {
            lifecycleScope.launch {
                settingsRepository.resetPlan(usageRepository.totalMobileBytes(), settings.nextAutoResetDate())
            }
        }
    }

    private fun applyVpnMode(mode: VpnMode, whitelist: Set<String>) {
        if (mode == lastAppliedMode && (mode != VpnMode.FILTER || whitelist == lastAppliedWhitelist)) return
        lastAppliedMode = mode
        lastAppliedWhitelist = whitelist
        when (mode) {
            VpnMode.STOPPED -> ContextCompat.startForegroundService(this, FirewallVpnService.stopIntent(this))
            VpnMode.FILTER -> ContextCompat.startForegroundService(
                this, FirewallVpnService.startFilterIntent(this, whitelist)
            )
            VpnMode.BLOCK_ALL -> ContextCompat.startForegroundService(
                this, FirewallVpnService.startBlockAllIntent(this)
            )
        }
    }

    private fun buildNotification(snapshot: MonitorSnapshot): Notification {
        val nm = getSystemService(NotificationManager::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                getString(R.string.notification_channel_name),
                NotificationManager.IMPORTANCE_LOW
            ).apply { description = getString(R.string.notification_channel_description) }
            nm.createNotificationChannel(channel)
        }

        val (emoji, stateLabel, color) = when {
            !snapshot.serviceRunning -> Triple("⚪", getString(R.string.dashboard_control_disabled), Color.parseColor("#757575"))
            snapshot.dataState == DataState.OK -> Triple("🟢", getString(R.string.dashboard_state_ok), Color.parseColor("#2E7D32"))
            snapshot.dataState == DataState.WARNING -> Triple("🟠", getString(R.string.dashboard_state_warning), Color.parseColor("#EF6C00"))
            snapshot.dataState == DataState.CUT -> Triple("🔴", getString(R.string.dashboard_state_cut), Color.parseColor("#C62828"))
            else -> Triple("🔵", getString(R.string.dashboard_state_wifi), Color.parseColor("#1565C0"))
        }

        val percentText = if (snapshot.planLimitMo > 0) {
            "${(snapshot.consumedMo * 100 / snapshot.planLimitMo)}%"
        } else "—"
        val priceText = String.format("%.2f", snapshot.estimatedTotalPriceEuro)

        val contentText = "$emoji $stateLabel · ${snapshot.consumedMo}/${snapshot.planLimitMo} Mo ($percentText)"
        val bigText = "$contentText\n${getString(R.string.dashboard_cost_total)} : $priceText €"

        val openApp = PendingIntent.getActivity(
            this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE
        )

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(getString(R.string.notification_title))
            .setContentText(contentText)
            .setStyle(NotificationCompat.BigTextStyle().bigText(bigText))
            .setSmallIcon(R.drawable.ic_notification)
            .setColor(color)
            .setColorized(true)
            .setOngoing(true)
            .setOnlyAlertOnce(true)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setContentIntent(openApp)
            .build()
    }

    private fun updateNotification(snapshot: MonitorSnapshot) {
        val nm = getSystemService(NotificationManager::class.java)
        nm.notify(NOTIF_ID, buildNotification(snapshot))
    }

    override fun onDestroy() {
        runCatching { unregisterReceiver(vpnRevokedReceiver) }
        super.onDestroy()
    }

    companion object {
        private const val CHANNEL_ID = "datacontrol_monitor"
        private const val NOTIF_ID = 1001
        private const val TICK_INTERVAL_MS = 20_000L
        private const val PULSE_DURATION_MS = 30_000L
        private const val WARNING_THRESHOLD = 0.8f

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, DataMonitorService::class.java))
        }

        fun stop(context: Context) {
            context.stopService(Intent(context, DataMonitorService::class.java))
        }
    }
}
