package com.erwan.revwatch

import android.app.Activity
import android.app.WallpaperManager
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.os.Build
import android.os.Bundle
import android.view.HapticFeedbackConstants
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.viewpager2.widget.ViewPager2
import com.erwan.revwatch.databinding.ActivityDashboardBinding
import com.erwan.revwatch.model.CellConfig
import com.erwan.revwatch.model.DataBinding
import com.erwan.revwatch.model.DashPage
import com.erwan.revwatch.model.GaugeRange
import com.erwan.revwatch.model.MetricSnapshot
import com.erwan.revwatch.model.WidgetType
import com.erwan.revwatch.prefs.AppPrefs
import com.erwan.revwatch.ui.VerticalPagerAdapter
import kotlinx.serialization.json.Json
import java.io.File

class DashboardActivity : BaseActivity() {

    private lateinit var binding: ActivityDashboardBinding
    private lateinit var verticalAdapter: VerticalPagerAdapter

    private val json = Json { ignoreUnknownKeys = true }
    private var lastStatus: String? = null
    private var isEditMode = false
    private val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
    private var pendingSnapshot: MetricSnapshot? = null
    private var snapshotPostPending = false
    private var pendingIntro = false
    private var pendingIconPackage: String? = null
    private var pendingWallpaper = false

    companion object {
        const val EXTRA_SHOW_INTRO = "show_intro"
        const val ACTION_PLAY_OUTRO = "com.erwan.revwatch.PLAY_OUTRO"
    }

    private val gaugeConfigLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
        val data = result.data ?: return@registerForActivityResult

        val widgetKey = data.getStringExtra(GaugeConfigActivity.EXTRA_WIDGET_KEY) ?: return@registerForActivityResult
        val faceId = data.getStringExtra(GaugeConfigActivity.EXTRA_FACE_ID) ?: return@registerForActivityResult
        val unitKey = data.getStringExtra(GaugeConfigActivity.EXTRA_UNIT_KEY) ?: return@registerForActivityResult
        val pageIndex = data.getIntExtra(GaugeConfigActivity.EXTRA_PAGE_INDEX, -1)
        val cellIndex = data.getIntExtra(GaugeConfigActivity.EXTRA_CELL_INDEX, -1)

        val widget = WidgetType.fromKey(widgetKey) ?: return@registerForActivityResult
        val range = GaugeRange.deserialize(data.getStringExtra(GaugeConfigActivity.EXTRA_RANGE))

        // Parse extra bindings
        val extraBindingsStr = data.getStringExtra(GaugeConfigActivity.EXTRA_BINDINGS)
        val extraBindings = if (!extraBindingsStr.isNullOrBlank()) {
            extraBindingsStr.split(";").mapNotNull { DataBinding.deserialize(it) }
        } else emptyList()

        if (pageIndex >= 0 && cellIndex >= 0) {
            val cellConfig = CellConfig(widget, faceId, unitKey, range, extraBindings)
            AppPrefs.setCell(this, pageIndex, cellIndex, cellConfig)
            refreshConfig()
        }
    }

    private val imagePickerLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        if (uri == null) return@registerForActivityResult
        val pkg = pendingIconPackage ?: return@registerForActivityResult
        pendingIconPackage = null

        val dir = File(filesDir, "app_icons")
        dir.mkdirs()
        val outFile = File(dir, "$pkg.png")
        try {
            contentResolver.openInputStream(uri)?.use { input ->
                val bmp = BitmapFactory.decodeStream(input) ?: return@registerForActivityResult
                outFile.outputStream().use { out ->
                    bmp.compress(Bitmap.CompressFormat.PNG, 100, out)
                }
                bmp.recycle()
            }
            AppPrefs.setCustomIcon(this, pkg, "file:${outFile.absolutePath}")
            verticalAdapter.refreshApps()
        } catch (_: Exception) {}
    }

    private val wallpaperPickerLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        if (uri == null) return@registerForActivityResult
        val dir = File(filesDir, "wallpaper")
        dir.mkdirs()
        val outFile = File(dir, "bg.png")
        try {
            contentResolver.openInputStream(uri)?.use { input ->
                val bmp = BitmapFactory.decodeStream(input) ?: return@registerForActivityResult
                outFile.outputStream().use { out ->
                    bmp.compress(Bitmap.CompressFormat.PNG, 90, out)
                }
                bmp.recycle()
            }
            AppPrefs.setWallpaper(this, "image:${outFile.absolutePath}")
            applyWallpaper()
        } catch (_: Exception) {}
    }

    private val outroReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action != ACTION_PLAY_OUTRO) return
            val config = AppPrefs.load(this@DashboardActivity)
            binding.brandSplashOverlay.playOutro(config.vehicleProfile.brand) {
                lockScreen()
            }
        }
    }

    private val statusReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action != ObdBridgeService.ACTION_STATUS) return

            val status = intent.getStringExtra(ObdBridgeService.EXTRA_STATUS)
            if (!status.isNullOrBlank()) {
                lastStatus = status
            }

            val payload = intent.getStringExtra(ObdBridgeService.EXTRA_PAYLOAD)
            if (!payload.isNullOrBlank()) {
                try {
                    val snapshot = json.decodeFromString(MetricSnapshot.serializer(), payload)
                    verticalAdapter.updateSnapshot(snapshot)
                } catch (_: Exception) {}
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityDashboardBinding.inflate(layoutInflater)

        pendingIntro = intent.getBooleanExtra(EXTRA_SHOW_INTRO, false)
        if (pendingIntro) {
            binding.brandSplashOverlay.visibility = View.VISIBLE
            binding.brandSplashOverlay.alpha = 1f
        }

        setContentView(binding.root)
        enableImmersiveMode()
        applyWallpaper()
        setupVerticalPager()
        startBridgeService()
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        if (intent?.getBooleanExtra(EXTRA_SHOW_INTRO, false) == true) {
            pendingIntro = true
            binding.brandSplashOverlay.visibility = View.VISIBLE
            binding.brandSplashOverlay.alpha = 1f
        } else {
            // Home button press: go back to gauges, exit all edit modes
            goHome()
        }
    }

    override fun onResume() {
        super.onResume()
        refreshConfig()
        // Restore edit mode if we were editing before onPause (e.g. returning from GaugeConfigActivity)
        if (isEditMode) {
            binding.verticalPager.isUserInputEnabled = false
            verticalAdapter.setEditMode(true)
        }
        verticalAdapter.refreshApps()

        // Play pending intro now that we're visible
        if (pendingIntro) {
            pendingIntro = false
            val config = AppPrefs.load(this)
            binding.brandSplashOverlay.playIntro(config.vehicleProfile.brand) {}
        } else {
            // Hide overlay if it was shown by onStop() pre-cover (e.g. returning from GaugeConfigActivity)
            binding.brandSplashOverlay.cancel()
            binding.brandSplashOverlay.visibility = View.GONE
        }

        // Direct in-memory listener — snapshot arrives on IO thread, coalesce to main thread
        ObdBridgeService.snapshotListener = { snapshot ->
            pendingSnapshot = snapshot
            if (!snapshotPostPending) {
                snapshotPostPending = true
                mainHandler.post {
                    snapshotPostPending = false
                    pendingSnapshot?.let { verticalAdapter.updateSnapshot(it) }
                }
            }
        }
        ObdBridgeService.statusListener = { status ->
            mainHandler.post { lastStatus = status }
        }

        val statusFilter = IntentFilter(ObdBridgeService.ACTION_STATUS)
        val outroFilter = IntentFilter(ACTION_PLAY_OUTRO)
        if (Build.VERSION.SDK_INT >= 33) {
            registerReceiver(statusReceiver, statusFilter, RECEIVER_NOT_EXPORTED)
            registerReceiver(outroReceiver, outroFilter, RECEIVER_NOT_EXPORTED)
        } else {
            registerReceiver(statusReceiver, statusFilter)
            registerReceiver(outroReceiver, outroFilter)
        }
    }

    override fun onPause() {
        // Suspend edit mode visually but remember we were editing
        // so we can restore it in onResume after returning from GaugeConfigActivity
        if (isEditMode) {
            verticalAdapter.setEditMode(false)
            binding.verticalPager.isUserInputEnabled = true
        }
        verticalAdapter.exitAppsEditMode()

        ObdBridgeService.snapshotListener = null
        ObdBridgeService.statusListener = null
        try { unregisterReceiver(statusReceiver) } catch (_: Exception) {}
        try { unregisterReceiver(outroReceiver) } catch (_: Exception) {}
        super.onPause()
    }

    override fun onStop() {
        super.onStop()
        // Pre-cover the screen so next resume starts black (no gauge flicker)
        if (AppPrefs.getScreenChargeControl(this)) {
            binding.brandSplashOverlay.cancel()
            binding.brandSplashOverlay.visibility = View.VISIBLE
            binding.brandSplashOverlay.alpha = 1f
        }
    }

    @Deprecated("Use OnBackPressedCallback")
    override fun onBackPressed() {
        if (isEditMode) {
            exitEditMode()
            return
        }
        if (verticalAdapter.exitAppsEditMode()) return
        if (binding.verticalPager.currentItem > 0) {
            binding.verticalPager.currentItem = 0
            return
        }
        // Already on gauges page: do nothing (we're the launcher)
    }

    private fun goHome() {
        if (isEditMode) exitEditMode()
        verticalAdapter.exitAppsEditMode()
        binding.verticalPager.setCurrentItem(0, true)
    }

    // ──────── Vertical pager ────────

    private fun setupVerticalPager() {
        val config = AppPrefs.load(this)

        verticalAdapter = VerticalPagerAdapter(
            onCellTap = { pageIndex, cellIndex, existing -> launchGaugeConfig(pageIndex, cellIndex, existing) },
            onCellDelete = { pageIndex, cellIndex -> deleteCell(pageIndex, cellIndex) },
            onAddPage = { addNewPage() },
            onDeletePage = { pageIndex -> deletePage(pageIndex) },
            onLayoutChange = { pageIndex -> cyclePageLayout(pageIndex) },
            onExitEdit = { exitEditMode() },
            onLongPress = { enterEditMode() },
            onSettingChanged = { refreshConfig() },
            onPickCustomIcon = { pkg ->
                pendingIconPackage = pkg
                imagePickerLauncher.launch("image/*")
            },
            onWallpaperPick = { wallpaperPickerLauncher.launch("image/*") },
            onWallpaperColor = { color ->
                AppPrefs.setWallpaper(this, "color:$color")
                applyWallpaper()
            },
            onWallpaperReset = {
                AppPrefs.setWallpaper(this, null)
                applyWallpaper()
            }
        )

        binding.verticalPager.orientation = ViewPager2.ORIENTATION_VERTICAL
        binding.verticalPager.adapter = verticalAdapter
        binding.verticalPager.offscreenPageLimit = 1
        binding.verticalPager.isUserInputEnabled = !isEditMode

        binding.verticalPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                verticalAdapter.visiblePage = position
                // Refresh gauges with latest snapshot when returning to gauge page
                if (position == VerticalPagerAdapter.PAGE_GAUGES) {
                    verticalAdapter.updateSnapshot(verticalAdapter.latestSnapshot)
                }
            }
        })

        verticalAdapter.updateConfig(config)
    }

    private fun refreshConfig() {
        val config = AppPrefs.load(this)
        verticalAdapter.updateConfig(config)
        if (isEditMode) {
            verticalAdapter.setEditMode(true)
        }
    }

    // ──────── Edit mode ────────

    fun enterEditMode() {
        if (isEditMode) return
        isEditMode = true
        binding.root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
        binding.verticalPager.isUserInputEnabled = false
        verticalAdapter.setEditMode(true)
    }

    private fun exitEditMode() {
        if (!isEditMode) return
        isEditMode = false
        binding.verticalPager.isUserInputEnabled = true
        verticalAdapter.setEditMode(false)
    }

    private fun launchGaugeConfig(pageIndex: Int, cellIndex: Int, existing: CellConfig?) {
        val intent = Intent(this, GaugeConfigActivity::class.java).apply {
            putExtra(GaugeConfigActivity.EXTRA_PAGE_INDEX, pageIndex)
            putExtra(GaugeConfigActivity.EXTRA_CELL_INDEX, cellIndex)
            if (existing != null) {
                putExtra(GaugeConfigActivity.EXTRA_WIDGET_KEY, existing.widgetType.key)
                putExtra(GaugeConfigActivity.EXTRA_FACE_ID, existing.faceId)
                putExtra(GaugeConfigActivity.EXTRA_UNIT_KEY, existing.unitKey)
                existing.range?.let { putExtra(GaugeConfigActivity.EXTRA_RANGE, it.serialize()) }
                if (existing.extraBindings.isNotEmpty()) {
                    putExtra(GaugeConfigActivity.EXTRA_BINDINGS,
                        existing.extraBindings.joinToString(";") { it.serialize() })
                }
            }
        }
        gaugeConfigLauncher.launch(intent)
    }

    private fun deleteCell(pageIndex: Int, cellIndex: Int) {
        binding.root.performHapticFeedback(HapticFeedbackConstants.REJECT)
        AppPrefs.setCell(this, pageIndex, cellIndex, null)
        refreshConfig()
    }

    private fun addNewPage() {
        binding.root.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
        AppPrefs.addPage(this, DashPage(1, 1, listOf(null)))
        refreshConfig()
        enterEditMode()
    }

    private fun deletePage(pageIndex: Int) {
        binding.root.performHapticFeedback(HapticFeedbackConstants.REJECT)
        AppPrefs.removePage(this, pageIndex)
        refreshConfig()
    }

    private fun cyclePageLayout(pageIndex: Int) {
        val config = AppPrefs.load(this)
        if (pageIndex !in config.pages.indices) return
        val page = config.pages[pageIndex]
        val (newCols, newRows) = DashPage.nextLayout(page.columns, page.rows)
        AppPrefs.updatePageLayout(this, pageIndex, newCols, newRows)
        refreshConfig()
    }

    // ──────── Wallpaper ────────

    private fun applyWallpaper() {
        val wp = AppPrefs.getWallpaper(this)
        var systemBitmap: Bitmap? = null
        when {
            wp == null -> {
                binding.wallpaperImage.visibility = View.GONE
                binding.wallpaperColor.visibility = View.GONE
                systemBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888).apply {
                    eraseColor(android.graphics.Color.BLACK)
                }
            }
            wp.startsWith("image:") -> {
                val path = wp.removePrefix("image:")
                val bmp = BitmapFactory.decodeFile(path)
                if (bmp != null) {
                    binding.wallpaperImage.setImageBitmap(bmp)
                    binding.wallpaperImage.visibility = View.VISIBLE
                    binding.wallpaperColor.visibility = View.GONE
                    systemBitmap = bmp
                }
            }
            wp.startsWith("color:") -> {
                try {
                    val color = android.graphics.Color.parseColor(wp.removePrefix("color:"))
                    binding.wallpaperColor.setBackgroundColor(color)
                    binding.wallpaperColor.visibility = View.VISIBLE
                    binding.wallpaperImage.visibility = View.GONE
                    systemBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888).apply {
                        eraseColor(color)
                    }
                } catch (_: Exception) {}
            }
        }
        if (systemBitmap != null) {
            setSystemWallpaper(systemBitmap)
        }
    }

    private fun setSystemWallpaper(bmp: Bitmap) {
        try {
            val wm = WallpaperManager.getInstance(this)
            if (Build.VERSION.SDK_INT >= 24) {
                wm.setBitmap(bmp, null, true,
                    WallpaperManager.FLAG_SYSTEM or WallpaperManager.FLAG_LOCK)
            } else {
                wm.setBitmap(bmp)
            }
        } catch (_: Exception) {}
    }

    // ──────── Service ────────

    private fun startBridgeService() {
        val intent = Intent(this, ObdBridgeService::class.java).apply {
            action = ObdBridgeService.ACTION_START
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(intent)
        } else {
            startService(intent)
        }
    }

    private fun lockScreen() {
        val dpm = getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager
        val admin = ComponentName(this, ScreenAdminReceiver::class.java)
        if (dpm.isAdminActive(admin)) {
            dpm.lockNow()
        }
    }
}
