package wang.yaojia.webterm.screens

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import wang.yaojia.webterm.designsystem.Spacing
import wang.yaojia.webterm.designsystem.WebTermColors
import wang.yaojia.webterm.designsystem.WebTermTheme
import wang.yaojia.webterm.designsystem.WebTermType
import wang.yaojia.webterm.viewmodels.CertSummaryView
import wang.yaojia.webterm.viewmodels.EnrollError
import wang.yaojia.webterm.viewmodels.EnrollPhase
import wang.yaojia.webterm.viewmodels.EnrollmentUiState
import wang.yaojia.webterm.viewmodels.EnrollmentViewModel

/**
 * # EnrollmentScreen (B4) — zero-`.p12` device enrollment (the phone half of zero-touch).
 *
 * The Compose shell over [EnrollmentViewModel], reached from the session-list host menu "自动获取证书"
 * (mirrors iOS `EnrollmentScreen` presented from `SessionListScreen`'s host menu). One operator login
 * generates a NON-EXPORTABLE hardware key + CSR and obtains a device cert with no file at all; the cert is
 * committed to the shared store and presented automatically on the existing mTLS path (cache-refreshed, so
 * no restart). The manual `.p12` path ([ClientCertScreen]) remains available alongside this one.
 *
 * ### Secrets & trust discipline (plan §8)
 *  - The operator password is a masked field bound straight to the ViewModel and cleared after every
 *    attempt — never logged, persisted, or echoed. The private key is generated non-exportably in secure
 *    hardware inside the library; this screen never sees it.
 *  - Every cert-derived string (CNs, expiry) and every error message is inert [Text] — no autolink/markdown.
 *    Error copy is app-authored, never a server/exception string.
 *
 * The keystore/network I/O it drives is device-QA (plan §7); the state machine is JVM-tested in
 * `EnrollmentViewModelTest`.
 */
@Composable
public fun EnrollmentScreen(
    viewModel: EnrollmentViewModel,
    modifier: Modifier = Modifier,
    onBack: (() -> Unit)? = null,
) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()
    LaunchedEffect(viewModel) { viewModel.bind(this) }

    EnrollmentScreen(
        state = state,
        onControlPlaneUrlChange = viewModel::onControlPlaneUrlChange,
        onSubdomainChange = viewModel::onSubdomainChange,
        onDeviceNameChange = viewModel::onDeviceNameChange,
        onPasswordChange = viewModel::onPasswordChange,
        onEnroll = viewModel::enroll,
        onDismissError = viewModel::clearError,
        modifier = modifier,
        onBack = onBack,
    )
}

/** Stateless body — pure inputs so a [Preview] and any host can drive it without the VM machinery. */
@Composable
public fun EnrollmentScreen(
    state: EnrollmentUiState,
    onControlPlaneUrlChange: (String) -> Unit,
    onSubdomainChange: (String) -> Unit,
    onDeviceNameChange: (String) -> Unit,
    onPasswordChange: (String) -> Unit,
    onEnroll: () -> Unit,
    onDismissError: () -> Unit,
    modifier: Modifier = Modifier,
    onBack: (() -> Unit)? = null,
) {
    Surface(modifier = modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
                .padding(Spacing.lg16),
            verticalArrangement = Arrangement.spacedBy(Spacing.md12),
        ) {
            Header(onBack = onBack)

            if (state.phase == EnrollPhase.LOADING) {
                LoadingRow()
                return@Column
            }

            InstalledSection(summary = state.summary)

            state.error?.let { ErrorCard(error = it, onDismiss = onDismissError) }
            if (state.didSucceed && state.error == null) SuccessCard()

            EnrollForm(
                state = state,
                onControlPlaneUrlChange = onControlPlaneUrlChange,
                onSubdomainChange = onSubdomainChange,
                onDeviceNameChange = onDeviceNameChange,
                onPasswordChange = onPasswordChange,
                onEnroll = onEnroll,
            )

            Text(
                text = EnrollCopy.FOOTER,
                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
            )
        }
    }
}

@Composable
private fun Header(onBack: (() -> Unit)?) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(Spacing.sm8),
    ) {
        if (onBack != null) {
            TextButton(onClick = onBack) { Text("返回") }
        }
        Text(text = EnrollCopy.TITLE, style = MaterialTheme.typography.headlineSmall)
    }
}

/** The currently-installed identity summary (or "none" copy) — the "已安装证书" section. */
@Composable
private fun InstalledSection(summary: CertSummaryView?) {
    if (summary == null) {
        Text(
            text = EnrollCopy.NONE_INSTALLED,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
        return
    }
    Card(
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(Spacing.lg16),
            verticalArrangement = Arrangement.spacedBy(Spacing.sm8),
        ) {
            SummaryRow(label = "设备（CN）", value = summary.subjectCommonName)
            SummaryRow(label = "签发方（CN）", value = summary.issuerCommonName)
            SummaryRow(label = "到期", value = summary.expiry)
            if (summary.isExpired) {
                HorizontalDivider(color = MaterialTheme.colorScheme.outline)
                Text(
                    text = "⚠ 证书已过期，请重新注册。",
                    style = MaterialTheme.typography.bodyMedium,
                    color = WebTermColors.statusStuck,
                )
            }
        }
    }
}

@Composable
private fun SummaryRow(label: String, value: String) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(Spacing.md12),
    ) {
        Text(
            text = label,
            style = MaterialTheme.typography.labelMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            modifier = Modifier.weight(1f),
        )
        // Cert-derived value: inert monospaced Text — no linkify/markdown (plan §8).
        Text(text = value, style = WebTermType.monoTabular(13), color = MaterialTheme.colorScheme.onSurface)
    }
}

@Composable
private fun EnrollForm(
    state: EnrollmentUiState,
    onControlPlaneUrlChange: (String) -> Unit,
    onSubdomainChange: (String) -> Unit,
    onDeviceNameChange: (String) -> Unit,
    onPasswordChange: (String) -> Unit,
    onEnroll: () -> Unit,
) {
    val enrolling = state.phase == EnrollPhase.ENROLLING
    Text(
        text = if (state.summary == null) EnrollCopy.ENROLL_HEADER else EnrollCopy.ROTATE_HEADER,
        style = MaterialTheme.typography.titleSmall,
    )
    OutlinedTextField(
        value = state.controlPlaneUrl,
        onValueChange = onControlPlaneUrlChange,
        label = { Text(EnrollCopy.CONTROL_PLANE_URL) },
        singleLine = true,
        enabled = !enrolling,
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
        modifier = Modifier.fillMaxWidth(),
    )
    OutlinedTextField(
        value = state.subdomain,
        onValueChange = onSubdomainChange,
        label = { Text(EnrollCopy.SUBDOMAIN) },
        singleLine = true,
        enabled = !enrolling,
        modifier = Modifier.fillMaxWidth(),
    )
    OutlinedTextField(
        value = state.deviceName,
        onValueChange = onDeviceNameChange,
        label = { Text(EnrollCopy.DEVICE_NAME) },
        singleLine = true,
        enabled = !enrolling,
        modifier = Modifier.fillMaxWidth(),
    )
    OutlinedTextField(
        value = state.password,
        onValueChange = onPasswordChange,
        label = { Text(EnrollCopy.PASSWORD) },
        singleLine = true,
        enabled = !enrolling,
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
        modifier = Modifier.fillMaxWidth(),
    )
    Button(
        onClick = onEnroll,
        enabled = state.canEnroll,
        modifier = Modifier.fillMaxWidth(),
    ) {
        if (enrolling) {
            CircularProgressIndicator(modifier = Modifier.padding(Spacing.xs4))
        } else {
            Text(if (state.summary == null) EnrollCopy.ENROLL_ACTION else EnrollCopy.ROTATE_ACTION)
        }
    }
}

@Composable
private fun ErrorCard(error: EnrollError, onDismiss: () -> Unit) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(Spacing.md12),
            verticalArrangement = Arrangement.spacedBy(Spacing.xs4),
        ) {
            Text(
                text = errorCopy(error),
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onErrorContainer,
            )
            TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) { Text("知道了") }
        }
    }
}

@Composable
private fun SuccessCard() {
    Card(
        modifier = Modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
    ) {
        Text(
            text = EnrollCopy.SUCCESS,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurface,
            modifier = Modifier
                .fillMaxWidth()
                .padding(Spacing.md12),
        )
    }
}

@Composable
private fun LoadingRow() {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.Center,
    ) {
        CircularProgressIndicator()
    }
}

/** Maps the coarse [EnrollError] to inert, app-authored copy (never an exception/server string, §8). */
private fun errorCopy(error: EnrollError): String = when (error) {
    EnrollError.INVALID_URL -> "控制面地址无效，请输入 https:// 开头的完整地址。"
    EnrollError.MISSING_FIELDS -> "请填写子域名、设备名称和操作口令。"
    EnrollError.BAD_CREDENTIAL -> "操作口令错误，请重试。"
    EnrollError.SUBDOMAIN_NOT_OWNED -> "该账号未拥有此子域名，无法签发证书。"
    EnrollError.RATE_LIMITED -> "请求过于频繁，请稍后再试。"
    EnrollError.REJECTED -> "请求被拒绝：子域名或证书请求无效。"
    EnrollError.ENROLL_FAILED -> "注册失败，请稍后重试。"
    EnrollError.SERVER -> "服务器返回异常，请稍后重试。"
    EnrollError.KEYGEN -> "生成硬件密钥失败（本设备可能不支持安全硬件）。"
    EnrollError.UNKNOWN -> "注册失败，请重试。"
}

/** User-facing copy (Chinese), mirroring iOS `EnrollmentCopy`. */
private object EnrollCopy {
    const val TITLE = "自动获取证书"
    const val NONE_INSTALLED = "尚未安装设备证书。"
    const val ENROLL_HEADER = "注册本设备"
    const val ROTATE_HEADER = "重新注册"
    const val CONTROL_PLANE_URL = "控制面地址"
    const val SUBDOMAIN = "子域名（你拥有的隧道名）"
    const val DEVICE_NAME = "设备名称"
    const val PASSWORD = "操作口令"
    const val ENROLL_ACTION = "注册本设备"
    const val ROTATE_ACTION = "重新注册"
    const val SUCCESS = "已注册，证书已保存到本设备安全硬件，连接隧道主机时将自动出示。"
    const val FOOTER =
        "首次登录一次即可：本设备在安全硬件（StrongBox / TEE）生成不可导出的私钥，向控制面申请证书并自动保存；" +
            "之后连接隧道主机时自动出示，无需再手动导入 .p12。"
}

@Preview(name = "EnrollmentScreen — none installed")
@Composable
private fun EnrollmentScreenNonePreview() {
    WebTermTheme {
        EnrollmentScreen(
            state = EnrollmentUiState(
                controlPlaneUrl = "https://cp.terminal.yaojia.wang",
                subdomain = "alice",
                deviceName = "Pixel 8",
                phase = EnrollPhase.IDLE,
            ),
            onControlPlaneUrlChange = {},
            onSubdomainChange = {},
            onDeviceNameChange = {},
            onPasswordChange = {},
            onEnroll = {},
            onDismissError = {},
        )
    }
}

@Preview(name = "EnrollmentScreen — installed + error")
@Composable
private fun EnrollmentScreenInstalledPreview() {
    WebTermTheme {
        EnrollmentScreen(
            state = EnrollmentUiState(
                controlPlaneUrl = "https://cp.terminal.yaojia.wang",
                subdomain = "alice",
                deviceName = "Pixel 8",
                summary = CertSummaryView("alice-pixel", "webterm-device-ca", "2027年1月8日", isExpired = false),
                phase = EnrollPhase.IDLE,
                error = EnrollError.SUBDOMAIN_NOT_OWNED,
            ),
            onControlPlaneUrlChange = {},
            onSubdomainChange = {},
            onDeviceNameChange = {},
            onPasswordChange = {},
            onEnroll = {},
            onDismissError = {},
        )
    }
}
