package wang.yaojia.webterm.screens

import android.Manifest
import android.content.pm.PackageManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.view.LifecycleCameraController
import androidx.camera.view.PreviewView
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.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
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.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.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import wang.yaojia.webterm.designsystem.Spacing
import wang.yaojia.webterm.hostregistry.Host
import wang.yaojia.webterm.viewmodels.EntryError
import wang.yaojia.webterm.viewmodels.PairingUiState
import wang.yaojia.webterm.viewmodels.PairingViewModel
import wang.yaojia.webterm.viewmodels.WarningTier
import wang.yaojia.webterm.viewmodels.needsExplicitAck

/**
 * # PairingScreen (A19) — QR scan / manual URL entry → confirm-before-network → probe → warning tiers.
 *
 * The Compose shell over [PairingViewModel]. Two ways in — a **CameraX + ML Kit** QR analyzer or a
 * **manual URL field** — both funnel their string through the ViewModel's single
 * [HostEndpoint.fromBaseUrl][wang.yaojia.webterm.wire.HostEndpoint] validator; NEITHER auto-connects
 * (confirm-before-network, plan §5). The confirm card renders the §5.4 warning tier; a public host
 * requires an explicit acknowledge, and a tunnel host routes to the device-cert screen when the probe is
 * cert-gated. Every displayed URL/host is INERT [Text] (no autolink/markdown, plan §8).
 *
 * ### Permissions
 *  - **CAMERA** (QR only) is requested at runtime with a rationale; a denial degrades gracefully to
 *    manual entry (the QR path is never mandatory).
 *  - **POST_NOTIFICATIONS** is NOT requested here — push registration owns that prompt (A31); pairing
 *    only establishes the host.
 *
 * All Compose / CameraX / permission behaviour is device-QA (plan §7); the tier/gate logic it drives is
 * JVM-tested in `PairingViewModelTest`.
 *
 * @param viewModel the pairing state machine (nav layer builds it with the real [pairingProber]).
 * @param onPaired invoked once with the persisted [Host] after a successful pair.
 * @param onImportCert opens the device-cert screen (A27) when a tunnel host is cert-gated.
 */
@Composable
public fun PairingScreen(
    viewModel: PairingViewModel,
    onPaired: (Host) -> Unit,
    onImportCert: () -> Unit,
    modifier: Modifier = Modifier,
) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(Spacing.lg16),
        verticalArrangement = Arrangement.spacedBy(Spacing.md12),
    ) {
        Text(text = "配对主机", style = MaterialTheme.typography.headlineSmall)

        when (val s = state) {
            is PairingUiState.Entry -> EntryStep(
                error = s.error,
                onManual = viewModel::onManualEntry,
                onScanned = viewModel::onQrScanned,
            )
            is PairingUiState.Confirming -> ConfirmStep(
                state = s,
                onName = viewModel::setName,
                onConfirm = viewModel::confirm,
                onCancel = viewModel::reset,
            )
            is PairingUiState.Probing -> ProbingStep(host = s.name)
            is PairingUiState.CertRequired -> CertRequiredStep(
                host = s.name,
                onImportCert = onImportCert,
                onRetry = { viewModel.retry() },
                onCancel = viewModel::reset,
            )
            is PairingUiState.Failed -> FailedStep(
                message = s.message,
                needsAck = s.tier.needsExplicitAck,
                onRetry = { ack -> viewModel.retry(acknowledgedPublicRisk = ack) },
                onCancel = viewModel::reset,
            )
            is PairingUiState.Paired -> LaunchedEffect(s.host) { onPaired(s.host) }
        }
    }
}

// ── Entry: manual URL or QR scan ─────────────────────────────────────────────────────────────────────

@Composable
private fun EntryStep(
    error: EntryError?,
    onManual: (String) -> Unit,
    onScanned: (String) -> Unit,
) {
    var scanning by remember { mutableStateOf(false) }
    var url by remember { mutableStateOf("") }

    Row(horizontalArrangement = Arrangement.spacedBy(Spacing.sm8)) {
        TextButton(onClick = { scanning = false }) { Text(if (scanning) "手动输入" else "● 手动输入") }
        TextButton(onClick = { scanning = true }) { Text(if (scanning) "● 扫码" else "扫码") }
    }

    if (scanning) {
        QrScanner(onScanned = onScanned)
        Text("将服务器分享二维码对准取景框。", style = MaterialTheme.typography.bodySmall)
        return
    }

    OutlinedTextField(
        value = url,
        onValueChange = { url = it },
        label = { Text("主机地址（http(s)://…）") },
        singleLine = true,
        isError = error != null,
        modifier = Modifier.fillMaxWidth(),
    )
    if (error == EntryError.INVALID_URL) {
        Text(
            text = "请输入有效的 http(s):// 地址。",
            color = MaterialTheme.colorScheme.error,
            style = MaterialTheme.typography.bodySmall,
        )
    }
    Button(
        onClick = { onManual(url) },
        enabled = url.isNotBlank(),
        modifier = Modifier.fillMaxWidth(),
    ) { Text("下一步") }
}

/**
 * The CameraX + ML Kit QR analyzer, gated on the CAMERA runtime permission. A denial shows a rationale
 * + re-request; the caller always offers the manual path so QR is never mandatory.
 */
@Composable
private fun QrScanner(onScanned: (String) -> Unit) {
    val context = LocalContext.current
    var granted by remember {
        mutableStateOf(
            ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) ==
                PackageManager.PERMISSION_GRANTED,
        )
    }
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
        granted = it
    }
    LaunchedEffect(Unit) {
        if (!granted) launcher.launch(Manifest.permission.CAMERA)
    }

    if (granted) {
        CameraPreview(onScanned = onScanned)
    } else {
        Column(verticalArrangement = Arrangement.spacedBy(Spacing.sm8)) {
            Text("需要相机权限才能扫描配对二维码。你也可以改用手动输入。")
            Button(onClick = { launcher.launch(Manifest.permission.CAMERA) }) { Text("授予相机权限") }
        }
    }
}

@Composable
private fun CameraPreview(onScanned: (String) -> Unit) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val controller = remember { LifecycleCameraController(context) }

    DisposableEffect(controller, lifecycleOwner) {
        controller.setImageAnalysisAnalyzer(
            ContextCompat.getMainExecutor(context),
            QrCodeAnalyzer(onScanned),
        )
        controller.bindToLifecycle(lifecycleOwner)
        onDispose { controller.unbind() }
    }

    AndroidView(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(1f),
        factory = { ctx -> PreviewView(ctx).also { it.controller = controller } },
    )
}

// ── Confirm: warning tier + name + (public) acknowledge ──────────────────────────────────────────────

@Composable
private fun ConfirmStep(
    state: PairingUiState.Confirming,
    onName: (String) -> Unit,
    onConfirm: (Boolean) -> Unit,
    onCancel: () -> Unit,
) {
    var acknowledged by remember(state.endpoint) { mutableStateOf(false) }

    TierWarningCard(tier = state.tier, highlight = state.awaitingAck && !acknowledged)
    // The dialed URL is validated but user/QR-sourced — render INERT (plan §8).
    Text(
        text = state.endpoint.baseUrl,
        style = MaterialTheme.typography.bodyMedium,
        maxLines = 1,
        overflow = TextOverflow.Ellipsis,
    )
    OutlinedTextField(
        value = state.name,
        onValueChange = onName,
        label = { Text("名称") },
        singleLine = true,
        modifier = Modifier.fillMaxWidth(),
    )

    if (state.tier.needsExplicitAck) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            Checkbox(checked = acknowledged, onCheckedChange = { acknowledged = it })
            Text("我了解这是公网地址的风险，仍要连接。")
        }
    }

    Row(horizontalArrangement = Arrangement.spacedBy(Spacing.sm8)) {
        OutlinedButton(onClick = onCancel, modifier = Modifier.weight(1f)) { Text("取消") }
        Button(
            onClick = { onConfirm(acknowledged) },
            enabled = !state.tier.needsExplicitAck || acknowledged,
            modifier = Modifier.weight(1f),
        ) { Text("连接") }
    }
}

@Composable
private fun TierWarningCard(tier: WarningTier, highlight: Boolean) {
    val (title, body) = tierCopy(tier)
    val container =
        if (highlight || tier == WarningTier.PUBLIC) MaterialTheme.colorScheme.errorContainer
        else MaterialTheme.colorScheme.surfaceVariant
    Card(colors = CardDefaults.cardColors(containerColor = container), shape = RoundedCornerShape(Spacing.md12)) {
        Column(modifier = Modifier.padding(Spacing.md12), verticalArrangement = Arrangement.spacedBy(Spacing.xs4)) {
            Text(text = title, style = MaterialTheme.typography.titleSmall)
            Text(text = body, style = MaterialTheme.typography.bodySmall)
        }
    }
}

private fun tierCopy(tier: WarningTier): Pair<String, String> = when (tier) {
    WarningTier.LOOPBACK -> "本机地址" to "指向本机，安全。"
    WarningTier.PRIVATE_LAN -> "局域网地址" to "同一局域网内以 ws:// 明文传输（未加密）。"
    WarningTier.TAILSCALE -> "Tailscale 地址" to "流量由 WireGuard 端到端加密。"
    WarningTier.TUNNEL -> "隧道主机" to "需要已安装的设备证书（mTLS）才能连接。"
    WarningTier.PUBLIC -> "公网地址（高风险）" to "任何能访问该地址的人都可能连到你的终端。请确认这是你信任的主机。"
}

// ── Probing / cert-required / failed ─────────────────────────────────────────────────────────────────

@Composable
private fun ProbingStep(host: String) {
    Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
        Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(Spacing.sm8)) {
            CircularProgressIndicator()
            Text("正在验证 $host …", style = MaterialTheme.typography.bodyMedium)
        }
    }
}

@Composable
private fun CertRequiredStep(
    host: String,
    onImportCert: () -> Unit,
    onRetry: () -> Unit,
    onCancel: () -> Unit,
) {
    Card(
        colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
        shape = RoundedCornerShape(Spacing.md12),
    ) {
        Column(modifier = Modifier.padding(Spacing.md12), verticalArrangement = Arrangement.spacedBy(Spacing.xs4)) {
            Text("需要设备证书", style = MaterialTheme.typography.titleSmall)
            Text("$host 是隧道主机，必须先导入设备证书（mTLS）才能配对。", style = MaterialTheme.typography.bodySmall)
        }
    }
    Row(horizontalArrangement = Arrangement.spacedBy(Spacing.sm8)) {
        OutlinedButton(onClick = onCancel, modifier = Modifier.weight(1f)) { Text("取消") }
        Button(onClick = onImportCert, modifier = Modifier.weight(1f)) { Text("导入证书") }
    }
    TextButton(onClick = onRetry, modifier = Modifier.fillMaxWidth()) { Text("已导入，重试") }
}

@Composable
private fun FailedStep(
    message: String,
    needsAck: Boolean,
    onRetry: (Boolean) -> Unit,
    onCancel: () -> Unit,
) {
    Card(
        colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
        shape = RoundedCornerShape(Spacing.md12),
    ) {
        // Inert copy (app-authored, never a raw server string — plan §8).
        Text(text = message, modifier = Modifier.padding(Spacing.md12), style = MaterialTheme.typography.bodyMedium)
    }
    Row(horizontalArrangement = Arrangement.spacedBy(Spacing.sm8)) {
        OutlinedButton(onClick = onCancel, modifier = Modifier.weight(1f)) { Text("返回") }
        // A prior public-host ack was consumed reaching the probe; carry it through the retry.
        Button(onClick = { onRetry(needsAck) }, modifier = Modifier.weight(1f)) { Text("重试") }
    }
}

// ── ML Kit QR analyzer ───────────────────────────────────────────────────────────────────────────────

/**
 * Extracts the first QR raw value from a CameraX frame and reports it ONCE. `@ExperimentalGetImage` is
 * required to read the backing `android.media.Image`; the analyzer always closes the [ImageProxy] so the
 * pipeline never stalls.
 */
@ExperimentalGetImage
private class QrCodeAnalyzer(private val onQr: (String) -> Unit) : ImageAnalysis.Analyzer {
    private val scanner = BarcodeScanning.getClient(
        BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_QR_CODE).build(),
    )
    private var reported = false

    override fun analyze(imageProxy: ImageProxy) {
        val media = imageProxy.image
        if (media == null || reported) {
            imageProxy.close()
            return
        }
        val input = InputImage.fromMediaImage(media, imageProxy.imageInfo.rotationDegrees)
        scanner.process(input)
            .addOnSuccessListener { barcodes ->
                barcodes.firstNotNullOfOrNull { it.rawValue }?.let { value ->
                    if (!reported) {
                        reported = true
                        onQr(value)
                    }
                }
            }
            .addOnCompleteListener { imageProxy.close() }
    }
}
