package de.softwareapp_hb.privateqrscanner.ui.screens

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.graphics.Paint
import android.net.Uri
import android.os.Build
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import de.softwareapp_hb.privateqrscanner.R
import de.softwareapp_hb.privateqrscanner.data.scanner.DetectionBox
import de.softwareapp_hb.privateqrscanner.data.scanner.DetectionPoint
import de.softwareapp_hb.privateqrscanner.domain.ScanResult
import de.softwareapp_hb.privateqrscanner.ui.theme.PrivateQrColors
import de.softwareapp_hb.privateqrscanner.util.readableBarcodePayload
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.math.max
import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine

private fun loadBitmapFromUri(context: android.content.Context, uri: Uri): Bitmap? {
    return try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val source = ImageDecoder.createSource(context.contentResolver, uri)
            ImageDecoder.decodeBitmap(source)
        } else {
            context.contentResolver.openInputStream(uri)?.use { stream ->
                BitmapFactory.decodeStream(stream)
            }
        }
    } catch (_: Exception) {
        null
    }
}

private suspend fun detectBarcodes(
    scanner: BarcodeScanner,
    image: InputImage
): List<Barcode> = suspendCancellableCoroutine { cont ->
    scanner.process(image)
        .addOnSuccessListener { barcodes ->
            if (cont.isActive) cont.resume(barcodes)
        }
        .addOnFailureListener { error ->
            if (cont.isActive) cont.resumeWithException(error)
        }
}

@Composable
internal fun GalleryScanPreviewDialog(
    imageUri: Uri?,
    candidates: List<GalleryScanCandidate>,
    onPick: (GalleryScanCandidate) -> Unit,
    onDismiss: () -> Unit
) {
    val context = LocalContext.current
    val bitmap = remember(imageUri) { imageUri?.let { loadBitmapFromUri(context, it) } }
    var liveCandidates by remember(imageUri, candidates) { mutableStateOf(candidates) }
    var selectedIndex by remember(imageUri) { mutableIntStateOf(0) }
    var zoom by remember(imageUri) { mutableFloatStateOf(1f) }
    var pan by remember(imageUri) { mutableStateOf(Offset.Zero) }
    var viewportSize by remember { mutableStateOf(IntSize.Zero) }
    var scanTick by remember { mutableIntStateOf(0) }
    val markerPaint = remember {
        Paint().apply {
            color = android.graphics.Color.WHITE
            textAlign = Paint.Align.CENTER
            textSize = 34f
            isAntiAlias = true
            isFakeBoldText = true
        }
    }
    val scanner = remember {
        BarcodeScanning.getClient(
            BarcodeScannerOptions.Builder()
                .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
                .enableAllPotentialBarcodes()
                .build()
        )
    }

    DisposableEffect(Unit) {
        onDispose { scanner.close() }
    }

    LaunchedEffect(liveCandidates.size) {
        selectedIndex = if (liveCandidates.isEmpty()) {
            0
        } else {
            selectedIndex.coerceIn(0, liveCandidates.lastIndex)
        }
    }

    LaunchedEffect(bitmap, viewportSize, zoom, pan, scanTick) {
        val bmp = bitmap ?: return@LaunchedEffect
        val vw = viewportSize.width.toFloat()
        val vh = viewportSize.height.toFloat()
        if (vw <= 0f || vh <= 0f) return@LaunchedEffect

        delay(120)

        val imgW = bmp.width.toFloat()
        val imgH = bmp.height.toFloat()
        if (imgW <= 1f || imgH <= 1f) return@LaunchedEffect

        val baseScale = max(vw / imgW, vh / imgH)
        val effectiveScale = (baseScale * zoom).coerceAtLeast(0.01f)
        val cx = vw * 0.5f
        val cy = vh * 0.5f

        fun screenToImageX(screenX: Float): Float {
            return ((screenX - cx - pan.x) / effectiveScale) + (imgW * 0.5f)
        }
        fun screenToImageY(screenY: Float): Float {
            return ((screenY - cy - pan.y) / effectiveScale) + (imgH * 0.5f)
        }

        val left = screenToImageX(0f).coerceIn(0f, imgW - 1f)
        val right = screenToImageX(vw).coerceIn(0f, imgW - 1f)
        val top = screenToImageY(0f).coerceIn(0f, imgH - 1f)
        val bottom = screenToImageY(vh).coerceIn(0f, imgH - 1f)

        val cropLeft = minOf(left, right).toInt()
        val cropTop = minOf(top, bottom).toInt()
        val cropW = (kotlin.math.abs(right - left)).toInt().coerceAtLeast(8)
        val cropH = (kotlin.math.abs(bottom - top)).toInt().coerceAtLeast(8)
        val boundedW = cropW.coerceAtMost(bmp.width - cropLeft)
        val boundedH = cropH.coerceAtMost(bmp.height - cropTop)
        if (boundedW <= 4 || boundedH <= 4) return@LaunchedEffect

        val cropped = Bitmap.createBitmap(bmp, cropLeft, cropTop, boundedW, boundedH)
        val barcodes = try {
            detectBarcodes(scanner, InputImage.fromBitmap(cropped, 0))
        } catch (_: Exception) {
            emptyList()
        }

        val live = barcodes.mapNotNull { barcode ->
            val payload = barcode.readableBarcodePayload() ?: return@mapNotNull null
            val normalizedBox = barcode.boundingBox?.let { bounds ->
                val leftN = ((bounds.left + cropLeft) / imgW).coerceIn(0f, 1f)
                val topN = ((bounds.top + cropTop) / imgH).coerceIn(0f, 1f)
                val rightN = ((bounds.right + cropLeft) / imgW).coerceIn(0f, 1f)
                val bottomN = ((bounds.bottom + cropTop) / imgH).coerceIn(0f, 1f)
                val corners = barcode.cornerPoints?.map { p ->
                    DetectionPoint(
                        x = ((p.x + cropLeft) / imgW).coerceIn(0f, 1f),
                        y = ((p.y + cropTop) / imgH).coerceIn(0f, 1f)
                    )
                } ?: emptyList()
                DetectionBox(leftN, topN, rightN, bottomN, corners)
            }
            GalleryScanCandidate(
                result = ScanResult(
                    content = payload.content,
                    type = barcode.valueType.toHumanType(),
                    isBase64Encoded = payload.isBase64Encoded
                ),
                box = normalizedBox
            )
        }.distinctBy { "${it.result.displayType}|${it.result.content}" }

        liveCandidates = live
    }

    AlertDialog(
        onDismissRequest = onDismiss,
        containerColor = PrivateQrColors.Surface,
        shape = RoundedCornerShape(30.dp),
        title = {
            Text(
                text = stringResource(R.string.image_scan_pick_title, liveCandidates.size),
                color = PrivateQrColors.TextPrimary,
                style = MaterialTheme.typography.headlineSmall,
                fontWeight = FontWeight.ExtraBold
            )
        },
        text = {
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .verticalScroll(rememberScrollState()),
                verticalArrangement = Arrangement.spacedBy(12.dp)
            ) {
                Text(
                    text = if (liveCandidates.isEmpty()) {
                        stringResource(R.string.no_code_found_in_image)
                    } else {
                        stringResource(R.string.image_scan_pick_subtitle)
                    },
                    color = PrivateQrColors.TextSecondary,
                    style = MaterialTheme.typography.bodyLarge,
                    fontWeight = FontWeight.SemiBold
                )
                if (bitmap != null) {
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                            .height(260.dp)
                            .onSizeChanged {
                                viewportSize = it
                                scanTick++
                            }
                            .pointerInput(bitmap) {
                                detectTransformGestures { _, panChange, zoomChange, _ ->
                                    zoom = (zoom * zoomChange).coerceIn(1f, 6f)
                                    pan += panChange
                                    scanTick++
                                }
                            }
                            .background(PrivateQrColors.Navy, RoundedCornerShape(24.dp)),
                        contentAlignment = Alignment.Center
                    ) {
                        Image(
                            bitmap = bitmap.asImageBitmap(),
                            contentDescription = stringResource(R.string.scan_from_image),
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(260.dp)
                                .graphicsLayer {
                                    scaleX = zoom
                                    scaleY = zoom
                                    translationX = pan.x
                                    translationY = pan.y
                                },
                            contentScale = ContentScale.Crop
                        )
                        Canvas(
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(260.dp)
                        ) {
                            val imageW = bitmap.width.toFloat()
                            val imageH = bitmap.height.toFloat()
                            if (imageW <= 0f || imageH <= 0f) return@Canvas

                            val baseScale = max(size.width / imageW, size.height / imageH)
                            val effectiveScale = baseScale * zoom
                            val cx = size.width * 0.5f
                            val cy = size.height * 0.5f

                            fun imageToScreen(ix: Float, iy: Float): Offset {
                                val sx = cx + ((ix - imageW * 0.5f) * effectiveScale) + pan.x
                                val sy = cy + ((iy - imageH * 0.5f) * effectiveScale) + pan.y
                                return Offset(sx, sy)
                            }

                            liveCandidates.forEachIndexed { index, candidate ->
                                val box = candidate.box ?: return@forEachIndexed
                                    val color = PrivateQrColors.Teal300.copy(alpha = 0.96f)
                                val points = box.corners.map { p ->
                                    imageToScreen(p.x * imageW, p.y * imageH)
                                }
                                if (points.size >= 4) {
                                    val path = Path().apply {
                                        moveTo(points.first().x, points.first().y)
                                        points.drop(1).forEach { pt -> lineTo(pt.x, pt.y) }
                                        close()
                                    }
                                    drawPath(path = path, color = color, style = Stroke(width = 4f))
                                } else {
                                    val lt = imageToScreen(box.left * imageW, box.top * imageH)
                                    val rb = imageToScreen(box.right * imageW, box.bottom * imageH)
                                    val left = minOf(lt.x, rb.x)
                                    val top = minOf(lt.y, rb.y)
                                    val right = maxOf(lt.x, rb.x)
                                    val bottom = maxOf(lt.y, rb.y)
                                    if (right > left && bottom > top) {
                                        drawRoundRect(
                                            color = color,
                                            topLeft = Offset(left, top),
                                            size = Size(right - left, bottom - top),
                                            cornerRadius = CornerRadius(10f, 10f),
                                            style = Stroke(width = 4f)
                                        )
                                    }
                                }

                                val center = if (points.isNotEmpty()) {
                                    val sx = points.sumOf { it.x.toDouble() }.toFloat() / points.size
                                    val sy = points.sumOf { it.y.toDouble() }.toFloat() / points.size
                                    Offset(sx, sy)
                                } else {
                                    imageToScreen(
                                        (box.left + box.right) * 0.5f * imageW,
                                        (box.top + box.bottom) * 0.5f * imageH
                                    )
                                }
                                drawCircle(
                                    color = Color.Black.copy(alpha = 0.65f),
                                    radius = 16f,
                                    center = center
                                )
                                drawContext.canvas.nativeCanvas.drawText(
                                    "${index + 1}",
                                    center.x,
                                    center.y + 11f,
                                    markerPaint
                                )
                            }
                        }
                    }
                }

                if (liveCandidates.isNotEmpty()) {
                    liveCandidates.forEachIndexed { index, candidate ->
                        val selected = index == selectedIndex
                        Row(
                            modifier = Modifier
                                .fillMaxWidth()
                                .background(
                                    color = if (selected) Color(0xFFECFDF5) else PrivateQrColors.AppBackground,
                                    shape = RoundedCornerShape(24.dp)
                                )
                                .border(
                                    width = 2.dp,
                                    color = if (selected) PrivateQrColors.Teal300 else Color.Transparent,
                                    shape = RoundedCornerShape(24.dp)
                                )
                                .clickable { selectedIndex = index }
                                .padding(18.dp),
                            horizontalArrangement = Arrangement.spacedBy(14.dp),
                            verticalAlignment = Alignment.CenterVertically
                        ) {
                            Box(
                                modifier = Modifier
                                    .size(54.dp)
                                    .background(PrivateQrColors.Mint, RoundedCornerShape(16.dp)),
                                contentAlignment = Alignment.Center
                            ) {
                                Text(
                                    text = "${index + 1}",
                                    color = PrivateQrColors.Teal700,
                                    style = MaterialTheme.typography.titleMedium,
                                    fontWeight = FontWeight.ExtraBold
                                )
                            }
                            Column(modifier = Modifier.weight(1f)) {
                                Text(
                                    text = candidate.result.localizedDisplayType(context),
                                    color = PrivateQrColors.TextPrimary,
                                    style = MaterialTheme.typography.titleMedium,
                                    fontWeight = FontWeight.ExtraBold
                                )
                                Text(
                                    text = if (candidate.result.isBase64Encoded) {
                                        stringResource(R.string.base64_encoded_inline, candidate.result.content)
                                    } else {
                                        candidate.result.content
                                    },
                                    color = PrivateQrColors.TextSecondary,
                                    style = MaterialTheme.typography.bodyMedium,
                                    fontWeight = FontWeight.SemiBold,
                                    maxLines = 2,
                                    overflow = TextOverflow.Ellipsis
                                )
                            }
                        }
                    }
                }
            }
        },
        confirmButton = {
            val selected = liveCandidates.getOrNull(selectedIndex)
            TextButton(
                onClick = {
                    if (selected != null) onPick(selected)
                },
                enabled = selected != null,
                colors = ButtonDefaults.textButtonColors(
                    containerColor = PrivateQrColors.Teal700,
                    contentColor = PrivateQrColors.Surface,
                    disabledContainerColor = PrivateQrColors.TextSecondary.copy(alpha = 0.18f),
                    disabledContentColor = PrivateQrColors.TextSecondary
                ),
                shape = RoundedCornerShape(18.dp)
            ) {
                Text(stringResource(R.string.image_scan_use_selected))
            }
        },
        dismissButton = {
            TextButton(
                onClick = onDismiss,
                colors = ButtonDefaults.textButtonColors(contentColor = PrivateQrColors.Teal700)
            ) {
                Text(stringResource(R.string.cancel))
            }
        }
    )
}
