package com.erwan.revwatch

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.util.Log
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import com.erwan.revwatch.model.ConnectionMode
import com.erwan.revwatch.model.MetricSnapshot
import com.erwan.revwatch.model.VehicleProfile
import com.erwan.revwatch.obd.ConnectionFactory
import com.erwan.revwatch.obd.Elm327Client
import com.erwan.revwatch.obd.ObdLogger
import com.erwan.revwatch.obd.ObdPoller
import com.erwan.revwatch.obd.OpenTransportResult
import com.erwan.revwatch.prefs.AppPrefs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.math.sin

class ObdBridgeService : Service() {

    companion object {
        const val ACTION_START = "com.erwan.revwatch.START"
        const val ACTION_STOP = "com.erwan.revwatch.STOP"
        const val ACTION_STATUS = "com.erwan.revwatch.STATUS"
        const val EXTRA_STATUS = "status"
        const val EXTRA_PAYLOAD = "payload"

        private const val CHANNEL_ID = "obd_bridge"
        private const val NOTIF_ID = 1001

        /** Direct in-memory snapshot listener — avoids JSON serialization + broadcast overhead. */
        var snapshotListener: ((MetricSnapshot) -> Unit)? = null

        /** Direct in-memory status listener. */
        var statusListener: ((String) -> Unit)? = null
    }

    private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val json = Json { prettyPrint = false }
    private var lastNotifText: String? = null

    private var loopJob: Job? = null
    private var elmClient: Elm327Client? = null
    private var poller: ObdPoller? = null
    private var activeMode: ConnectionMode? = null
    private var activeVehicle: VehicleProfile? = null
    private var simulationTick: Double = 0.0

    // ── Simulation state with inertia ──
    private var simThrottle = 0.0    // 0..1 driver throttle input
    private var simRpm = 850.0       // engine RPM (idles at 850)
    private var simSpeed = 0.0       // km/h
    private var simBoost = 0.0       // kPa above baro
    private var simCoolant = 78.0    // °C
    private var simOil = 82.0        // °C
    private var simVoltage = 13.8    // V
    private var simFuelLevel = 72.0  // %
    private var simGear = 1          // current gear

    private var powerReceiverRegistered = false

    private val powerReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (!AppPrefs.getScreenChargeControl(this@ObdBridgeService)) return
            when (intent?.action) {
                Intent.ACTION_POWER_CONNECTED -> wakeScreen()
                Intent.ACTION_POWER_DISCONNECTED -> lockScreen()
            }
        }
    }

    override fun onCreate() {
        super.onCreate()
        registerPowerReceiver()
        createNotificationChannel()
        startForeground(NOTIF_ID, buildNotification("Initialisation"))
        // Start debug logging
        ObdLogger.start(this)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            ACTION_STOP -> {
                stopSelf()
                return START_NOT_STICKY
            }
            else -> startLoopIfNeeded()
        }
        return START_STICKY
    }

    override fun onDestroy() {
        loopJob?.cancel()
        closeConnection()
        try { if (powerReceiverRegistered) unregisterReceiver(powerReceiver) } catch (_: Exception) {}
        ObdLogger.stop(this)
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    private fun startLoopIfNeeded() {
        if (loopJob?.isActive == true) {
            return
        }

        loopJob = serviceScope.launch {
            var consecutiveEmptySnapshots = 0
            var reconnectDelay = 1500L
            var cachedConfig = AppPrefs.load(this@ObdBridgeService)
            var configRefreshCounter = 0

            while (isActive) {
                // Refresh config every ~20 iterations (~7s) instead of every iteration
                if (++configRefreshCounter >= 20) {
                    configRefreshCounter = 0
                    cachedConfig = AppPrefs.load(this@ObdBridgeService)
                }
                val config = cachedConfig
                val inSimulation = config.connectionMode == ConnectionMode.TEST

                val needsConnection = if (inSimulation) {
                    activeMode != config.connectionMode || activeVehicle != config.vehicleProfile
                } else {
                    elmClient == null || activeMode != config.connectionMode || activeVehicle != config.vehicleProfile
                }

                if (needsConnection) {
                    closeConnection()
                    cachedConfig = AppPrefs.load(this@ObdBridgeService)
                    activeMode = config.connectionMode
                    activeVehicle = config.vehicleProfile
                    simulationTick = 0.0
                    consecutiveEmptySnapshots = 0

                    ObdLogger.logConfig(
                        config.connectionMode.name, config.vehicleProfile.displayName
                    )

                    if (inSimulation) {
                        simThrottle = 0.0; simRpm = 850.0; simSpeed = 0.0
                        simBoost = 0.0; simCoolant = 78.0; simOil = 82.0
                        simVoltage = 13.8; simGear = 1
                        emitStatus("Mode test actif")
                        ObdLogger.logConnection("Simulation mode started")
                        updateNotification("Simulation ${config.vehicleProfile.displayName}")
                    } else {
                        emitStatus("Connexion ${config.connectionMode.displayName}...")
                        updateNotification("Connexion...")
                        ObdLogger.logConnection("Ouverture transport: ${config.connectionMode.name}")

                        when (val result = ConnectionFactory.openTransport(this@ObdBridgeService, config)) {
                            is OpenTransportResult.Ready -> {
                                val elm = Elm327Client(result.transport)
                                val initialized = try {
                                    elm.initialize()
                                } catch (_: Exception) {
                                    false
                                }

                                if (!initialized) {
                                    elm.close()
                                    emitStatus("ELM non initialise")
                                    ObdLogger.logConnection("ELM init FAILED")
                                } else {
                                    elm.setBusViaRts(false)
                                    elmClient = elm
                                    poller = ObdPoller(elm, config.vehicleProfile)
                                    reconnectDelay = 1500L
                                    emitStatus("Connecte")
                                    ObdLogger.logConnection("Connecte et initialise OK")
                                    updateNotification("Connecte: ${config.vehicleProfile.displayName}")
                                }
                            }
                            is OpenTransportResult.Error -> {
                                emitStatus(result.message)
                                ObdLogger.logConnection("Erreur transport: ${result.message}")
                            }
                        }
                    }

                    // Always retry connection — never give up
                    if (!inSimulation && elmClient == null) {
                        emitStatus("Reconnexion dans ${reconnectDelay / 1000}s...")
                        delay(reconnectDelay)
                        // Exponential backoff: 1.5s → 3s → 5s → max 5s
                        reconnectDelay = (reconnectDelay * 2).coerceAtMost(5000L)
                        continue
                    }
                }

                if (inSimulation) {
                    emitPayload(nextSimulationSnapshot())
                    updateNotification("Streaming Test")
                    delay(350)
                    continue
                }

                val currentPoller = poller
                if (currentPoller == null) {
                    delay(500)
                    continue
                }

                try {
                    val snapshot = currentPoller.pollSnapshot()

                    // Detect "silent death": all values null = adapter hung or dead
                    val hasAnyValue = snapshot.rpm != null || snapshot.speedKph != null ||
                            snapshot.coolantTempC != null || snapshot.voltage != null
                    if (hasAnyValue) {
                        consecutiveEmptySnapshots = 0
                        emitPayload(snapshot)
                        updateNotification("Streaming ${config.connectionMode.displayName}")
                    } else {
                        consecutiveEmptySnapshots++
                        // Emit the empty snapshot too (shows "--" on gauges)
                        emitPayload(snapshot)

                        // Log ELM327 diagnostics on first empty snapshots
                        if (consecutiveEmptySnapshots <= 3) {
                            val elm = elmClient
                            if (elm != null) {
                                val logs = synchronized(elm.recentLog) { elm.recentLog.toList() }
                                Log.w("OBD", "Empty snapshot #$consecutiveEmptySnapshots. ELM log:")
                                logs.takeLast(15).forEach { Log.w("OBD", "  $it") }
                            }
                        }

                        if (consecutiveEmptySnapshots >= 8) {
                            // 8 consecutive empty snapshots = connection is dead
                            emitStatus("Pas de donnees, reconnexion...")
                            ObdLogger.logConnection("8 empty snapshots — declaring connection dead, reconnecting")
                            ObdLogger.flush()
                            closeConnection()
                            consecutiveEmptySnapshots = 0
                            delay(2000)
                            continue
                        }
                    }
                    delay(50)
                } catch (e: Exception) {
                    // IOException from write = connection dead, reconnect
                    emitStatus("Perte connexion, reconnexion...")
                    ObdLogger.logConnection("Exception in poll loop: ${e.javaClass.simpleName}: ${e.message}")
                    ObdLogger.flush()
                    closeConnection()
                    consecutiveEmptySnapshots = 0
                    delay(1000)
                }
            }
        }
    }

    /**
     * Simulation with a simplified physical model:
     * - Throttle follows a smooth sine pattern (driver behaviour)
     * - RPM responds to throttle with turbo lag / inertia
     * - Boost is coupled to RPM × throttle (turbo spools up with RPM)
     * - Speed follows RPM via gear ratios
     * - Coolant / oil rise slowly under load, fall slowly when idle
     * - Voltage dips under electrical load (high RPM / accessories)
     * - Auto gear shifts at redline / low RPM
     *
     * All transitions use exponential smoothing (lerp toward target)
     * so values are always continuous and physically plausible.
     */
    private fun nextSimulationSnapshot(): MetricSnapshot {
        simulationTick += 0.35  // ~one tick per 350ms delay
        val dt = 0.35           // seconds per tick

        // ── Throttle: smooth driver input ──
        // Slow sine (~25s full cycle) with gentle secondary variation
        val rawThrottle = (sin(simulationTick * 0.08) * 0.5 + 0.5)  // 0..1, slow
        val throttleNoise = sin(simulationTick * 0.31) * 0.05        // tiny flutter
        val targetThrottle = (rawThrottle + throttleNoise).coerceIn(0.0, 1.0)
        simThrottle = lerp(simThrottle, targetThrottle, 0.15)

        // ── RPM: responds to throttle with inertia ──
        val targetRpm = 850 + simThrottle * 5200   // idle 850 → redline ~6050
        val rpmResponse = if (targetRpm > simRpm) 0.12 else 0.08  // revs up faster than down
        simRpm = lerp(simRpm, targetRpm, rpmResponse)

        // ── Auto gear shift ──
        val gearRatios = doubleArrayOf(3.6, 2.1, 1.4, 1.0, 0.75, 0.62)  // 6 gears
        if (simRpm > 5800 && simGear < 6) simGear++
        if (simRpm < 1400 && simGear > 1) simGear--
        val ratio = gearRatios[simGear - 1]

        // ── Speed: follows RPM / gear ratio with road inertia ──
        val targetSpeed = (simRpm / ratio / 35.0).coerceIn(0.0, 220.0)
        simSpeed = lerp(simSpeed, targetSpeed, 0.10)

        // ── Boost: turbo spools proportional to RPM × throttle ──
        // Turbo needs RPM > ~2000 to spool; vacuum at idle
        val spoolFactor = ((simRpm - 1500) / 3000.0).coerceIn(0.0, 1.0)
        val targetBoost = if (simThrottle > 0.15) {
            spoolFactor * simThrottle * 120.0  // max ~120 kPa under full boost
        } else {
            -8.0  // vacuum at idle/coast
        }
        // Turbo lag: slow spool-up (~1.5s to full), faster blow-off
        val boostResponse = if (targetBoost > simBoost) 0.08 else 0.20
        simBoost = lerp(simBoost, targetBoost, boostResponse)

        // ── MAP = baro + boost ──
        val baro = 99.5
        val map = (baro + simBoost).coerceAtLeast(20.0)

        // ── Coolant: rises slowly under load, falls slowly at idle ──
        val loadFactor = simThrottle * (simRpm / 6000.0)
        val targetCoolant = 78.0 + loadFactor * 18.0  // 78°C idle → ~96°C full load
        simCoolant = lerp(simCoolant, targetCoolant, 0.01)  // very slow thermal response
        simCoolant = simCoolant.coerceIn(70.0, 105.0)

        // ── Oil: follows coolant with delay, runs ~8°C hotter ──
        val targetOil = simCoolant + 8.0 + loadFactor * 10.0
        simOil = lerp(simOil, targetOil, 0.008)  // even slower than coolant
        simOil = simOil.coerceIn(76.0, 130.0)

        // ── Voltage: alternator compensates, dips under high electrical load ──
        val targetVoltage = 14.1 - (simRpm / 6000.0) * 0.3 - loadFactor * 0.15
        simVoltage = lerp(simVoltage, targetVoltage, 0.10)
        simVoltage = simVoltage.coerceIn(13.2, 14.5)

        // ── Fuel: consumption based on load, level decreases slowly ──
        val fuelConsumption = (simRpm / 6000.0) * simThrottle * 15.0 + 0.8
        simFuelLevel = (simFuelLevel - fuelConsumption * dt / 3600.0).coerceIn(0.0, 100.0)

        return MetricSnapshot(
            ts = System.currentTimeMillis(),
            rpm = simRpm,
            speedKph = simSpeed,
            coolantTempC = simCoolant,
            oilTempC = simOil,
            mapKpa = map,
            baroKpa = baro,
            boostKpa = simBoost,
            voltage = simVoltage,
            fuelLevelPct = simFuelLevel,
            fuelRateLph = fuelConsumption,
            custom = mapOf("mode" to "simulation")
        )
    }

    /** Exponential smoothing: moves `current` toward `target` by `factor` (0..1). */
    private fun lerp(current: Double, target: Double, factor: Double): Double =
        current + (target - current) * factor

    private fun closeConnection() {
        try {
            elmClient?.close()
        } catch (_: Exception) {
        }
        elmClient = null
        poller = null
        activeVehicle = null
        // Emit empty snapshot so gauges show "--" immediately
        emitPayload(MetricSnapshot(ts = System.currentTimeMillis()))
    }

    private fun registerPowerReceiver() {
        val filter = IntentFilter().apply {
            addAction(Intent.ACTION_POWER_CONNECTED)
            addAction(Intent.ACTION_POWER_DISCONNECTED)
        }
        if (Build.VERSION.SDK_INT >= 33) {
            registerReceiver(powerReceiver, filter, RECEIVER_NOT_EXPORTED)
        } else {
            registerReceiver(powerReceiver, filter)
        }
        powerReceiverRegistered = true
    }

    @Suppress("DEPRECATION")
    private fun wakeScreen() {
        val pm = getSystemService(POWER_SERVICE) as PowerManager
        val wl = pm.newWakeLock(
            PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
            "revwatch:wakeup"
        )
        wl.acquire(5000L)

        val intent = Intent(this, DashboardActivity::class.java).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
            putExtra(DashboardActivity.EXTRA_SHOW_INTRO, true)
        }
        startActivity(intent)
    }

    private fun lockScreen() {
        val intent = Intent(DashboardActivity.ACTION_PLAY_OUTRO).apply {
            setPackage(applicationContext.packageName)
        }
        sendBroadcast(intent)
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= 26) {
            val channel = NotificationChannel(CHANNEL_ID, "RevWatch", NotificationManager.IMPORTANCE_LOW)
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(channel)
        }
    }

    private fun buildNotification(text: String): Notification {
        val openIntent = Intent(this, DashboardActivity::class.java)
        val openPendingIntent = PendingIntent.getActivity(
            this,
            10,
            openIntent,
            if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_IMMUTABLE else 0
        )

        val stopIntent = Intent(this, ObdBridgeService::class.java).apply { action = ACTION_STOP }
        val stopPendingIntent = PendingIntent.getService(
            this,
            11,
            stopIntent,
            if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_IMMUTABLE else 0
        )

        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("RevWatch")
            .setContentText(text)
            .setSmallIcon(android.R.drawable.stat_notify_sync)
            .setContentIntent(openPendingIntent)
            .setOngoing(true)
            .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopPendingIntent)
            .build()
    }

    private fun updateNotification(text: String) {
        if (text == lastNotifText) return
        lastNotifText = text
        val manager = getSystemService(NotificationManager::class.java)
        manager.notify(NOTIF_ID, buildNotification(text))
    }

    private fun emitStatus(status: String) {
        statusListener?.invoke(status)
        val intent = Intent(ACTION_STATUS).apply {
            setPackage(applicationContext.packageName)
            putExtra(EXTRA_STATUS, status)
        }
        sendBroadcast(intent)
    }

    private fun emitPayload(snapshot: MetricSnapshot) {
        val listener = snapshotListener
        if (listener != null) {
            listener.invoke(snapshot)
            return  // Skip broadcast+JSON when direct listener is active
        }
        val intent = Intent(ACTION_STATUS).apply {
            setPackage(applicationContext.packageName)
            putExtra(EXTRA_PAYLOAD, json.encodeToString(snapshot))
        }
        sendBroadcast(intent)
    }
}
