package de.tsschulz.tt_tagebuch.app.ui

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectDragGestures
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.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import de.tsschulz.tt_tagebuch.app.AppDependencies
import de.tsschulz.tt_tagebuch.shared.api.models.Member
import de.tsschulz.tt_tagebuch.shared.i18n.MobileStrings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt

private val CropFrameMaxHeight = 420.dp
private const val CropOutputSize = 600
private const val MinSelectionPx = 24f
private const val SmallCropThresholdPx = 80f

private fun decodeBitmapForCrop(resolver: android.content.ContentResolver, uri: Uri, maxSide: Int = 4096): Bitmap? {
    return resolver.openInputStream(uri)?.use { input ->
        val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
        BitmapFactory.decodeStream(input, null, bounds)
        if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return@use null
        var sample = 1
        var w = bounds.outWidth
        var h = bounds.outHeight
        while (w > maxSide || h > maxSide) {
            sample *= 2
            w = bounds.outWidth / sample
            h = bounds.outHeight / sample
        }
        val opts = BitmapFactory.Options().apply { inSampleSize = sample }
        resolver.openInputStream(uri)?.use { stream ->
            BitmapFactory.decodeStream(stream, null, opts)
        }
    }
}

private fun bitmapToJpeg(bitmap: Bitmap, quality: Int = 90): ByteArray {
    val out = ByteArrayOutputStream()
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, out)
    return out.toByteArray()
}

private fun cropSquareForUpload(
    source: Bitmap,
    selX: Float,
    selY: Float,
    selSize: Float,
    dispW: Float,
    dispH: Float,
): Bitmap? {
    if (selSize < MinSelectionPx || dispW <= 0f || dispH <= 0f) return null
    val bw = source.width.toFloat()
    val bh = source.height.toFloat()
    val scaleX = bw / dispW
    val scaleY = bh / dispH
    val sx = (selX * scaleX).roundToInt().coerceIn(0, source.width - 1)
    val sy = (selY * scaleY).roundToInt().coerceIn(0, source.height - 1)
    val sideRaw = (selSize * min(scaleX, scaleY)).roundToInt().coerceAtLeast(1)
    val side = min(sideRaw, min(source.width - sx, source.height - sy))
    if (side < 8) return null
    val cropped = Bitmap.createBitmap(source, sx, sy, side, side)
    return if (cropped.width == CropOutputSize && cropped.height == CropOutputSize) {
        cropped
    } else {
        val scaled = Bitmap.createScaledBitmap(cropped, CropOutputSize, CropOutputSize, true)
        if (scaled != cropped) cropped.recycle()
        scaled
    }
}

@Composable
fun MemberGroupPortraitCropRoute(
    clubId: Int,
    members: List<Member>,
    dependencies: AppDependencies,
    canWriteMembers: Boolean,
    onBack: () -> Unit,
) {
    val languageCode = LocalLanguageCode.current
    fun s(key: String, fb: String) = MobileStrings.get(languageCode, key, fb)
    val context = LocalContext.current
    val density = LocalDensity.current
    val scope = rememberCoroutineScope()

    var sourceUri by remember { mutableStateOf<Uri?>(null) }
    var fullBitmap by remember { mutableStateOf<Bitmap?>(null) }
    var decodeError by remember { mutableStateOf<String?>(null) }
    var containerSize by remember { mutableStateOf(IntSize.Zero) }

    var selectionStart by remember { mutableStateOf<Offset?>(null) }
    var selectionRect by remember { mutableStateOf<Rect?>(null) }
    var isDragging by remember { mutableStateOf(false) }

    var previewBytes by remember { mutableStateOf<ByteArray?>(null) }
    var previewBitmap by remember { mutableStateOf<Bitmap?>(null) }

    var memberQuery by remember { mutableStateOf("") }
    var selectedMemberId by remember { mutableStateOf<Int?>(null) }
    var makePrimary by remember { mutableStateOf(true) }
    var memberPickerOpen by remember { mutableStateOf(false) }
    var saving by remember { mutableStateOf(false) }
    var statusMessage by remember { mutableStateOf<String?>(null) }
    var errorMessage by remember { mutableStateOf<String?>(null) }

    BackHandler(onBack = onBack)

    LaunchedEffect(sourceUri) {
        val uri = sourceUri ?: return@LaunchedEffect
        decodeError = null
        fullBitmap?.recycle()
        fullBitmap = null
        selectionRect = null
        selectionStart = null
        previewBytes = null
        previewBitmap?.recycle()
        previewBitmap = null
        val bmp = withContext(Dispatchers.IO) {
            decodeBitmapForCrop(context.contentResolver, uri)
        }
        if (bmp == null) {
            decodeError = s("members.groupCropDecodeError", "Bild konnte nicht geladen werden.")
        } else {
            fullBitmap = bmp
        }
    }

    val pickImage = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
        if (uri != null) sourceUri = uri
    }

    fun resetSelection() {
        selectionStart = null
        selectionRect = null
        isDragging = false
        previewBytes = null
        previewBitmap?.recycle()
        previewBitmap = null
    }

    fun recomputePreviewFromSelection(rect: Rect?) {
        val bmp = fullBitmap ?: return
        if (rect == null || rect.width < MinSelectionPx || rect.height < MinSelectionPx) {
            previewBytes = null
            previewBitmap?.recycle()
            previewBitmap = null
            return
        }
        val frame = computeImageDisplaySize(bmp.width.toFloat(), bmp.height.toFloat(), containerSize)
        if (frame.dispW <= 0f || frame.dispH <= 0f) return
        val cropped = cropSquareForUpload(bmp, rect.left, rect.top, rect.width, frame.dispW, frame.dispH)
        if (cropped == null) {
            previewBytes = null
            previewBitmap?.recycle()
            previewBitmap = null
            return
        }
        previewBitmap?.recycle()
        previewBitmap = cropped
        previewBytes = bitmapToJpeg(cropped)
    }

    fun updateSelectionFromDrag(start: Offset, current: Offset, maxW: Float, maxH: Float) {
        val dx = current.x - start.x
        val dy = current.y - start.y
        var size = min(abs(dx), abs(dy))
        var x = if (dx < 0) start.x - size else start.x
        var y = if (dy < 0) start.y - size else start.y
        x = max(0f, min(x, maxW - size))
        y = max(0f, min(y, maxH - size))
        size = min(size, min(maxW - x, maxH - y))
        selectionRect = Rect(x, y, x + size, y + size)
    }

    val filteredMembers = remember(members, memberQuery) {
        val q = memberQuery.trim().lowercase()
        members
            .filter { m ->
                if (q.isEmpty()) return@filter true
                "${m.firstName} ${m.lastName}".lowercase().contains(q) ||
                    "${m.lastName} ${m.firstName}".lowercase().contains(q)
            }
            .sortedWith(compareBy<Member> { it.lastName.lowercase() }.thenBy { it.firstName.lowercase() })
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .imePadding()
            .navigationBarsPadding()
            .verticalScroll(rememberScrollState())
            .padding(horizontal = 20.dp, vertical = 16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text(
                s("members.groupPortraitCropTitle", "Portrait aus Gruppenfoto"),
                style = MaterialTheme.typography.h6,
            )
            TextButton(onClick = onBack) { Text(s("mobile.back", "Zurück")) }
        }
        Text(
            s(
                "members.groupPortraitCropHint",
                "Wähle ein Bild, ziehe mit dem Finger einen quadratischen Rahmen um eine Person, ordne das Mitglied zu und speichere.",
            ),
            style = MaterialTheme.typography.body2,
            color = MaterialTheme.colors.onSurface.copy(alpha = 0.75f),
            modifier = Modifier.padding(bottom = 12.dp),
        )

        if (!canWriteMembers) {
            Text(s("members.groupCropNoWrite", "Keine Berechtigung zum Hochladen von Mitgliedsfotos."))
            return@Column
        }

        OutlinedButton(
            onClick = {
                pickImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
            },
            modifier = Modifier
                .fillMaxWidth()
                .heightIn(min = 48.dp),
        ) { Text(s("members.groupCropPickImage", "Bild aus Galerie wählen")) }

        decodeError?.let {
            Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(top = 8.dp))
        }

        val bmp = fullBitmap
        if (bmp != null) {
            val frame = computeImageDisplaySize(
                bmp.width.toFloat(),
                bmp.height.toFloat(),
                containerSize,
            )
            val dispWdp = with(density) { frame.dispW.toDp() }
            val dispHdp = with(density) { frame.dispH.toDp() }

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(CropFrameMaxHeight)
                    .padding(top = 12.dp)
                    .onSizeChanged { containerSize = it },
                contentAlignment = Alignment.Center,
            ) {
                if (frame.dispW > 0f && frame.dispH > 0f) {
                    Box(
                        modifier = Modifier
                            .size(dispWdp, dispHdp)
                            .pointerInput(frame.dispW, frame.dispH, bmp) {
                                detectDragGestures(
                                    onDragStart = { start ->
                                        isDragging = true
                                        selectionStart = start
                                        errorMessage = null
                                        statusMessage = null
                                        previewBytes = null
                                        previewBitmap?.recycle()
                                        previewBitmap = null
                                        selectionRect = Rect(start.x, start.y, start.x, start.y)
                                    },
                                    onDrag = { change, _ ->
                                        val start = selectionStart ?: return@detectDragGestures
                                        updateSelectionFromDrag(start, change.position, frame.dispW, frame.dispH)
                                    },
                                    onDragEnd = {
                                        isDragging = false
                                        selectionStart = null
                                        val r = selectionRect
                                        if (r == null || r.width < MinSelectionPx) {
                                            resetSelection()
                                        } else {
                                            recomputePreviewFromSelection(r)
                                        }
                                    },
                                    onDragCancel = {
                                        isDragging = false
                                        selectionStart = null
                                        val r = selectionRect
                                        if (r == null || r.width < MinSelectionPx) {
                                            resetSelection()
                                        } else {
                                            recomputePreviewFromSelection(r)
                                        }
                                    },
                                )
                            },
                    ) {
                        Image(
                            bitmap = bmp.asImageBitmap(),
                            contentDescription = null,
                            modifier = Modifier.fillMaxSize(),
                        )
                        Canvas(Modifier.fillMaxSize()) {
                            val r = selectionRect
                            if (r != null && r.width > 0f && r.height > 0f) {
                                val path = Path().apply {
                                    addRect(Rect(Offset.Zero, size))
                                    addRect(r)
                                    fillType = PathFillType.EvenOdd
                                }
                                drawPath(path, Color.Black.copy(alpha = 0.48f))
                                drawRect(
                                    color = Color.White.copy(alpha = 0.85f),
                                    topLeft = Offset(r.left, r.top),
                                    size = Size(r.width, r.height),
                                    style = Stroke(width = 2.dp.toPx()),
                                )
                            }
                        }
                    }
                }
            }

            selectionRect?.let { r ->
                if (r.width < SmallCropThresholdPx && r.width >= MinSelectionPx && !isDragging) {
                    Text(
                        s("members.groupCropSmallWarning", "Kleiner Ausschnitt — Qualität kann leiden."),
                        style = MaterialTheme.typography.caption,
                        color = MaterialTheme.colors.secondary,
                        modifier = Modifier.padding(top = 6.dp),
                    )
                }
            }

            OutlinedButton(
                onClick = { resetSelection() },
                enabled = selectionRect != null && !isDragging,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 8.dp),
            ) { Text(s("members.groupCropResetSelection", "Auswahl zurücksetzen")) }

            Text(s("members.groupCropPreview", "Vorschau"), fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(top = 16.dp))
            val prev = previewBitmap
            if (prev != null) {
                Image(
                    bitmap = prev.asImageBitmap(),
                    contentDescription = s("members.groupCropPreview", "Vorschau"),
                    modifier = Modifier
                        .size(160.dp)
                        .padding(top = 6.dp),
                )
            } else {
                Text(
                    s("members.groupCropNoPreview", "Noch keine Auswahl."),
                    style = MaterialTheme.typography.caption,
                    modifier = Modifier.padding(top = 6.dp),
                )
            }

            OutlinedButton(
                onClick = { memberPickerOpen = true },
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 16.dp)
                    .heightIn(min = 48.dp),
            ) {
                Text(
                    selectedMemberId?.let { id ->
                        members.firstOrNull { it.id == id }?.let { "${it.lastName}, ${it.firstName}" }
                    } ?: s("members.groupCropPickMember", "Mitglied wählen"),
                )
            }

            Row(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier.padding(top = 8.dp),
            ) {
                Checkbox(checked = makePrimary, onCheckedChange = { makePrimary = it })
                Text(s("members.groupCropMakePrimary", "Als Hauptfoto verwenden"))
            }

            if (saving) {
                CircularProgressIndicator(modifier = Modifier.padding(16.dp))
            }

            Button(
                onClick = {
                    val bytes = previewBytes
                    val mid = selectedMemberId
                    if (bytes == null || mid == null) return@Button
                    saving = true
                    errorMessage = null
                    statusMessage = null
                    scope.launch {
                        runCatching {
                            dependencies.membersManager.uploadMemberPortrait(clubId, mid, bytes, makePrimary = makePrimary)
                            dependencies.membersManager.loadMembers(clubId)
                        }.fold(
                            onSuccess = {
                                val m = members.firstOrNull { it.id == mid }
                                statusMessage = m?.let {
                                    s("members.groupCropSavedFor", "Foto gespeichert für ${it.firstName} ${it.lastName}.")
                                } ?: s("members.groupCropSaved", "Foto gespeichert.")
                                resetSelection()
                            },
                            onFailure = {
                                errorMessage = it.message ?: s("members.groupCropSaveError", "Speichern fehlgeschlagen.")
                            },
                        )
                        saving = false
                    }
                },
                enabled = !saving && previewBytes != null && selectedMemberId != null,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 12.dp)
                    .heightIn(min = 48.dp),
            ) {
                Text(s("members.groupCropSave", "Als Mitgliedsfoto speichern"))
            }

            statusMessage?.let {
                Text(it, color = MaterialTheme.colors.primary, modifier = Modifier.padding(top = 8.dp))
            }
            errorMessage?.let {
                Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(top = 8.dp))
            }
        }

        Spacer(modifier = Modifier.height(24.dp))
    }

    if (memberPickerOpen) {
        StableAlertDialog(
            onDismissRequest = { memberPickerOpen = false },
            title = { Text(s("members.groupCropPickMember", "Mitglied wählen")) },
            text = {
                Column(modifier = Modifier.fillMaxWidth()) {
                    OutlinedTextField(
                        value = memberQuery,
                        onValueChange = { memberQuery = it },
                        label = { Text(s("mobile.search", "Suche")) },
                        singleLine = true,
                        modifier = Modifier.fillMaxWidth(),
                    )
                    LazyColumn(modifier = Modifier.height(320.dp).padding(top = 8.dp)) {
                        items(filteredMembers, key = { it.id }) { m ->
                            TextButton(
                                onClick = {
                                    selectedMemberId = m.id
                                    memberPickerOpen = false
                                },
                                modifier = Modifier.fillMaxWidth(),
                            ) {
                                Text("${m.lastName}, ${m.firstName}", modifier = Modifier.fillMaxWidth())
                            }
                        }
                    }
                }
            },
            confirmButton = {
                TextButton(onClick = { memberPickerOpen = false }) {
                    Text(s("common.close", "Schließen"))
                }
            },
        )
    }
}

private data class ImageDisplayFrame(val dispW: Float, val dispH: Float)

private fun computeImageDisplaySize(bitmapW: Float, bitmapH: Float, container: IntSize): ImageDisplayFrame {
    if (container.width <= 0 || container.height <= 0 || bitmapW <= 0f || bitmapH <= 0f) {
        return ImageDisplayFrame(0f, 0f)
    }
    val cw = container.width.toFloat()
    val ch = container.height.toFloat()
    val scale = min(cw / bitmapW, ch / bitmapH)
    return ImageDisplayFrame(bitmapW * scale, bitmapH * scale)
}
