// Copyright (C) 2026 Jason M. Schwefel. All Rights Reserved.
package com.coldboreballisticsllc.scopedope.ui.session

import android.app.Activity
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import com.coldboreballisticsllc.scopedope.BuildConfig
import com.coldboreballisticsllc.scopedope.data.prefs.AppPreferences
import com.coldboreballisticsllc.scopedope.data.prefs.StoredPrinterEntry
import com.coldboreballisticsllc.scopedope.domain.model.LabelEntry
import com.coldboreballisticsllc.scopedope.domain.model.WindLabel
import com.coldboreballisticsllc.scopedope.domain.printer.PrinterSource
import com.coldboreballisticsllc.scopedope.domain.printer.PrinterState
import com.coldboreballisticsllc.scopedope.domain.usecase.SavePngUseCase
import com.coldboreballisticsllc.scopedope.label.format.LabelFormat
import com.coldboreballisticsllc.scopedope.label.format.LabelShape
import com.coldboreballisticsllc.scopedope.label.renderer.CircleLabelRenderer
import com.coldboreballisticsllc.scopedope.label.renderer.ElevationSymbol
import com.coldboreballisticsllc.scopedope.label.renderer.RectLabelRenderer
import com.coldboreballisticsllc.scopedope.label.renderer.RenderOptions
import com.google.android.play.core.review.ReviewInfo
import com.google.android.play.core.review.ReviewManagerFactory
import javax.inject.Inject
import kotlin.math.roundToInt

sealed class SaveResult {
    data class Success(val uri: Uri) : SaveResult()
    data class Failure(val message: String) : SaveResult()
}

@Immutable
data class PrinterUiState(
    val connectedPrinterMac:    String? = null,
    val connectedPrinterBtName: String? = null,
    val connectedPrinterName:   String? = null,
    val printerSharedMode:      Boolean = false,
    val storedPrinters:         List<StoredPrinterEntry> = emptyList(),
    val printFullCircle:        Boolean = false,
    val showGridlines:          Boolean = true,
    val elevationSymbol:        ElevationSymbol = ElevationSymbol.PLUS_MINUS,
    val columnFillInterleaved:  Boolean = true,
    val landscapeCol1Reversed:  Boolean = true,
    val useSemanticColors:      Boolean = false,
    val saveResult:             SaveResult? = null,
)

@HiltViewModel
class PrinterViewModel @Inject constructor(
    @ApplicationContext private val context: Context,
    private val niimbot:          PrinterSource,
    private val prefs:            AppPreferences,
    internal val renderer:        CircleLabelRenderer,
    internal val rectRenderer:    RectLabelRenderer,
    private val savePng:      SavePngUseCase,
) : ViewModel() {

    private val _uiState = MutableStateFlow(PrinterUiState())
    val uiState: StateFlow<PrinterUiState> = _uiState.asStateFlow()

    val printerState: StateFlow<PrinterState> = niimbot.state

    val nativeDpi: Int get() = niimbot.nativeDpi

    private val reviewManager = ReviewManagerFactory.create(context)
    private val _pendingReview = MutableStateFlow<ReviewInfo?>(null)
    val pendingReview: StateFlow<ReviewInfo?> = _pendingReview.asStateFlow()

    init {
        viewModelScope.launch { loadPrefs() }

        viewModelScope.launch {
            niimbot.connectedDeviceInfo.collectLatest { info ->
                if (info != null) {
                    val storedName   = prefs.getPrinterName(info.mac)
                    val registryName = storedName ?: info.btName
                    prefs.upsertStoredPrinter(StoredPrinterEntry(info.mac, info.btName, registryName))
                    _uiState.update { it.copy(
                        connectedPrinterMac    = info.mac,
                        connectedPrinterBtName = info.btName,
                        connectedPrinterName   = storedName,
                    ) }
                } else {
                    _uiState.update { it.copy(
                        connectedPrinterMac    = null,
                        connectedPrinterBtName = null,
                        connectedPrinterName   = null,
                    ) }
                }
            }
        }

        viewModelScope.launch {
            prefs.printerRegistry.collect { list ->
                _uiState.update { it.copy(storedPrinters = list) }
            }
        }
    }

    private suspend fun loadPrefs() {
        val printFullCircle       = prefs.printFullCircle.first()
        val showGridlines         = prefs.showGridlines.first()
        val elevSymStr            = prefs.elevationSymbol.first()
        val columnFillInterleaved = prefs.columnFillInterleaved.first()
        val landscapeCol1Reversed = prefs.landscapeCol1Reversed.first()
        val useSemanticColors     = prefs.useSemanticColors.first()
        val printerSharedMode     = prefs.printerSharedMode.first()
        _uiState.update { it.copy(
            printFullCircle       = printFullCircle,
            showGridlines         = showGridlines,
            elevationSymbol       = if (elevSymStr == "ARROWS") ElevationSymbol.ARROWS else ElevationSymbol.PLUS_MINUS,
            columnFillInterleaved = columnFillInterleaved,
            landscapeCol1Reversed = landscapeCol1Reversed,
            useSemanticColors     = useSemanticColors,
            printerSharedMode     = printerSharedMode,
        ) }
    }

    fun connect() {
        val mac = _uiState.value.connectedPrinterMac
            ?: _uiState.value.storedPrinters.firstOrNull()?.mac
        viewModelScope.launch { niimbot.connect(mac) }
    }
    fun disconnect() { viewModelScope.launch { niimbot.disconnect() } }

    fun savePrinterName(name: String) {
        val mac    = _uiState.value.connectedPrinterMac    ?: return
        val btName = _uiState.value.connectedPrinterBtName ?: mac
        val stored = name.trim().ifEmpty { btName }
        viewModelScope.launch {
            prefs.upsertStoredPrinter(StoredPrinterEntry(mac, btName, stored))
            _uiState.update { it.copy(connectedPrinterName = stored) }
        }
    }

    fun setPrinterSharedMode(value: Boolean) {
        viewModelScope.launch {
            prefs.setPrinterSharedMode(value)
            _uiState.update { it.copy(printerSharedMode = value) }
        }
    }

    fun renamePrinter(mac: String, btName: String, newName: String) {
        val stored = newName.trim().ifEmpty { btName }
        viewModelScope.launch {
            prefs.upsertStoredPrinter(StoredPrinterEntry(mac, btName, stored))
            if (_uiState.value.connectedPrinterMac == mac) {
                _uiState.update { it.copy(connectedPrinterName = stored) }
            }
        }
    }

    fun removeStoredPrinter(mac: String) {
        viewModelScope.launch { prefs.removeStoredPrinter(mac) }
    }

    fun setPrintFullCircle(value: Boolean) {
        viewModelScope.launch {
            prefs.setPrintFullCircle(value)
            _uiState.update { it.copy(printFullCircle = value) }
        }
    }

    fun setShowGridlines(value: Boolean) {
        viewModelScope.launch {
            prefs.setShowGridlines(value)
            _uiState.update { it.copy(showGridlines = value) }
        }
    }

    fun setElevationSymbol(sym: ElevationSymbol) {
        viewModelScope.launch {
            prefs.setElevationSymbol(sym.name)
            _uiState.update { it.copy(elevationSymbol = sym) }
        }
    }

    fun setColumnFillInterleaved(value: Boolean) {
        viewModelScope.launch {
            prefs.setColumnFillInterleaved(value)
            _uiState.update { it.copy(columnFillInterleaved = value) }
        }
    }

    fun setLandscapeCol1Reversed(value: Boolean) {
        viewModelScope.launch {
            prefs.setLandscapeCol1Reversed(value)
            _uiState.update { it.copy(landscapeCol1Reversed = value) }
        }
    }

    fun setUseSemanticColors(value: Boolean) {
        viewModelScope.launch {
            prefs.setUseSemanticColors(value)
            _uiState.update { it.copy(useSemanticColors = value) }
        }
    }

    fun printViaNiimbot(
        entries:       List<LabelEntry>,
        windLabel:     WindLabel?,
        labelFormat:   LabelFormat,
        adjUnit:       AdjUnit,
        measureSystem: MeasureSystem,
        fullCircle:    Boolean = _uiState.value.printFullCircle,
    ) {
        viewModelScope.launch {
            val opts     = buildRenderOptions(adjUnit, measureSystem, fullCircle)
            val rendered = renderLabel(labelFormat, entries, windLabel, niimbot.nativeDpi, opts)
            val bitmap   = rotateBitmapForPrint(rendered, labelFormat)
            niimbot.print(bitmap)
            if (_uiState.value.printerSharedMode) niimbot.disconnect()
            requestReviewIfEligible()
        }
    }

    fun printDeviceIdLabel(labelFormat: LabelFormat) {
        viewModelScope.launch {
            val btName = _uiState.value.connectedPrinterBtName ?: return@launch
            val bitmap = renderIdLabelBitmap(btName, _uiState.value.connectedPrinterName, niimbot.nativeDpi, labelFormat)
            niimbot.print(bitmap)
        }
    }

    fun saveLabelAsPng(
        entries:       List<LabelEntry>,
        windLabel:     WindLabel?,
        labelFormat:   LabelFormat,
        adjUnit:       AdjUnit,
        measureSystem: MeasureSystem,
        caliber:       String,
    ) {
        viewModelScope.launch {
            val opts      = buildRenderOptions(adjUnit, measureSystem)
            val bitmap    = renderLabel(labelFormat, entries, windLabel, 203, opts)
            val safeCalib = caliber.replace(" ", "_").replace("/", "-")
            val result    = savePng.invoke(bitmap, "scopedope_$safeCalib")
            if (result.isSuccess) requestReviewIfEligible()
            _uiState.update {
                it.copy(saveResult = result.fold(SaveResult::Success) { e ->
                    SaveResult.Failure(e.message ?: "Unknown error")
                })
            }
        }
    }

    fun clearSaveResult() {
        _uiState.update { it.copy(saveResult = null) }
    }

    fun launchReview(activity: Activity) {
        val info = _pendingReview.value ?: return
        reviewManager.launchReviewFlow(activity, info).addOnCompleteListener {
            _pendingReview.value = null
            viewModelScope.launch {
                prefs.setLastReviewVersionCode(BuildConfig.VERSION_CODE)
            }
        }
    }

    fun clearPendingReview() {
        _pendingReview.value = null
    }

    private fun requestReviewIfEligible() {
        viewModelScope.launch {
            val lastReviewed = prefs.lastReviewVersionCode.first()
            if (lastReviewed >= BuildConfig.VERSION_CODE) return@launch
            reviewManager.requestReviewFlow().addOnCompleteListener { task ->
                if (task.isSuccessful) _pendingReview.value = task.result
            }
        }
    }

    private fun buildRenderOptions(
        adjUnit:       AdjUnit,
        measureSystem: MeasureSystem,
        fullCircle:    Boolean = _uiState.value.printFullCircle,
    ) = RenderOptions(
        adjDecimals           = if (adjUnit == AdjUnit.MOA) 2 else 1,
        showGridlines         = _uiState.value.showGridlines,
        fullCircle            = fullCircle,
        elevationSymbol       = _uiState.value.elevationSymbol,
        columnFillInterleaved = _uiState.value.columnFillInterleaved,
        landscapeCol1Reversed = _uiState.value.landscapeCol1Reversed,
        useMoa                = adjUnit == AdjUnit.MOA,
        useMetric             = measureSystem == MeasureSystem.METRIC,
        useSemanticColors     = _uiState.value.useSemanticColors,
    )

    private fun renderLabel(
        labelFormat: LabelFormat,
        entries:     List<LabelEntry>,
        windLabel:   WindLabel?,
        targetDpi:   Int,
        options:     RenderOptions,
    ): android.graphics.Bitmap {
        return if (labelFormat.shape is LabelShape.Circle) {
            renderer.render(entries, windLabel, targetDpi, options)
        } else {
            rectRenderer.render(labelFormat, entries, windLabel, targetDpi, options)
        }
    }

    private fun rotateBitmapForPrint(bitmap: android.graphics.Bitmap, format: LabelFormat): android.graphics.Bitmap {
        val rollWidthPx = (format.sizeGroup.rollWidthMm / 25.4f * niimbot.nativeDpi).roundToInt()
        if (bitmap.width == rollWidthPx) return bitmap
        val matrix = android.graphics.Matrix().apply { postRotate(90f) }
        return android.graphics.Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
    }

    private fun renderIdLabelBitmap(
        btName:       String,
        friendlyName: String?,
        dpi:          Int,
        format:       LabelFormat,
    ): android.graphics.Bitmap {
        val rollWidthPx = (format.sizeGroup.rollWidthMm / 25.4f * dpi).roundToInt()
        val bitmap = android.graphics.Bitmap.createBitmap(rollWidthPx, rollWidthPx, android.graphics.Bitmap.Config.RGB_565)
        bitmap.eraseColor(android.graphics.Color.WHITE)
        val canvas = android.graphics.Canvas(bitmap)
        val paint  = android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG).apply {
            color     = android.graphics.Color.BLACK
            textAlign = android.graphics.Paint.Align.CENTER
        }
        val cx    = rollWidthPx / 2f
        val maxPx = rollWidthPx * 0.88f
        paint.textSize = rollWidthPx * 0.14f
        if (paint.measureText(btName) > maxPx) {
            paint.textSize = paint.textSize * maxPx / paint.measureText(btName)
        }
        canvas.drawText(btName, cx, rollWidthPx * 0.42f, paint)
        if (!friendlyName.isNullOrBlank()) {
            paint.textSize = rollWidthPx * 0.10f
            if (paint.measureText(friendlyName) > maxPx) {
                paint.textSize = paint.textSize * maxPx / paint.measureText(friendlyName)
            }
            canvas.drawText(friendlyName, cx, rollWidthPx * 0.60f, paint)
        }
        paint.textSize = rollWidthPx * 0.07f
        paint.color    = android.graphics.Color.GRAY
        canvas.drawText("ScopeDOPE Printer", cx, rollWidthPx * 0.76f, paint)
        return rotateBitmapForPrint(bitmap, format)
    }
}
