package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa

import android.Manifest
import android.content.ClipData
import android.content.pm.PackageManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
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.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
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.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.QrCodeScanner
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
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.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.withFrameMillis
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.toClipEntry
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.elements.QrScannerDialog
import com.github.nullptroma.wallenc.ui.elements.WallencScreenContentPadding
import com.github.nullptroma.wallenc.ui.elements.WallencScreenScaffold
import com.github.nullptroma.wallenc.usecases.TwoFaCodeState
import com.github.nullptroma.wallenc.usecases.buildTwoFaCodeState
import com.github.nullptroma.wallenc.usecases.totpPeriodProgress
import com.github.nullptroma.wallenc.usecases.totpSecondsUntilRefresh
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlin.math.roundToInt

@Composable
fun TwoFaTokensScreen(
    modifier: Modifier = Modifier,
    viewModel: TwoFaTokensViewModel = hiltViewModel(),
) {
    val uiState by viewModel.state.collectAsStateWithLifecycle()
    val clipboard = LocalClipboard.current
    val scope = rememberCoroutineScope()
    val nowMillis by produceState(initialValue = System.currentTimeMillis()) {
        while (true) {
            withFrameMillis {
                value = System.currentTimeMillis()
            }
        }
    }
    var editingToken by remember { mutableStateOf<TwoFaTokenRecord?>(null) }
    var creating by remember { mutableStateOf(false) }

    WallencScreenScaffold(
        modifier = modifier,
        floatingActionButton = {
            FloatingActionButton(
                onClick = {
                    if (uiState.isAvailable && !uiState.isMutating) {
                        creating = true
                    }
                },
                modifier = Modifier.alpha(if (uiState.isAvailable && !uiState.isMutating) 1f else 0.5f),
            ) {
                Icon(Icons.Default.Add, contentDescription = stringResource(R.string.two_fa_add_token))
            }
        },
    ) { innerPadding ->
        WallencScreenContentPadding(innerPadding) {
        TwoFaTokensScreenContent(
            uiState = uiState,
            modifier = Modifier.fillMaxSize(),
        ) {
            LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
                items(uiState.items) { item ->
                        Card(
                            modifier = Modifier.fillMaxWidth(),
                            colors = CardDefaults.elevatedCardColors(
                                containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
                            ),
                        ) {
                            val codeState = rememberTwoFaCode(item, nowMillis)
                            Row(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(14.dp),
                                horizontalArrangement = Arrangement.SpaceBetween,
                            ) {
                                TwoFaTokenListHeader(
                                    issuer = item.issuer,
                                    account = item.account,
                                    modifier = Modifier.weight(1f),
                                )
                                Row {
                                    IconButton(
                                        onClick = { editingToken = item },
                                        enabled = uiState.isAvailable && !uiState.isMutating,
                                    ) {
                                        Icon(Icons.Default.Edit, contentDescription = stringResource(R.string.edit))
                                    }
                                    IconButton(
                                        onClick = { viewModel.deleteToken(item.id) },
                                        enabled = uiState.isAvailable && !uiState.isMutating,
                                    ) {
                                        Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.remove))
                                    }
                                }
                            }
                            val codeProgress = if (codeState == null) {
                                0f
                            } else {
                                totpPeriodProgress(nowMillis, item.periodSeconds)
                            }
                            val secondsUntilRefresh = codeState?.let {
                                totpSecondsUntilRefresh(nowMillis, item.periodSeconds)
                            }
                            Row(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(start = 14.dp, end = 14.dp, bottom = 10.dp),
                                horizontalArrangement = Arrangement.SpaceBetween,
                                verticalAlignment = Alignment.Top,
                            ) {
                                Column(
                                    verticalArrangement = Arrangement.spacedBy(2.dp),
                                ) {
                                    Text(
                                        text = stringResource(R.string.two_fa_code_refresh_label),
                                        style = MaterialTheme.typography.bodySmall,
                                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                                    )
                                    Text(
                                        text = if (secondsUntilRefresh != null) {
                                            stringResource(
                                                R.string.two_fa_code_refresh_seconds,
                                                secondsUntilRefresh,
                                            )
                                        } else {
                                            stringResource(R.string.two_fa_code_invalid_secret)
                                        },
                                        style = MaterialTheme.typography.bodySmall.copy(fontSize = 13.sp),
                                        color = if (codeState != null) {
                                            MaterialTheme.colorScheme.onSurfaceVariant
                                        } else {
                                            Color.Red
                                        },
                                    )
                                }
                                Card(
                                    onClick = {
                                        codeState?.let {
                                            scope.launch {
                                                val clipData = ClipData.newPlainText(it.code, it.code)
                                                clipboard.setClipEntry(clipData.toClipEntry())
                                            }
                                        }
                                    },
                                    enabled = codeState != null,
                                    colors = CardDefaults.cardColors(
                                        containerColor = MaterialTheme.colorScheme.surfaceContainer,
                                    ),
                                ) {
                                    Column(
                                        modifier = Modifier
                                            .padding(horizontal = 16.dp, vertical = 10.dp),
                                        verticalArrangement = Arrangement.spacedBy(4.dp),
                                    ) {
                                        Row(
                                            horizontalArrangement = Arrangement.spacedBy(8.dp),
                                            verticalAlignment = Alignment.CenterVertically,
                                        ) {
                                            Icon(
                                                imageVector = Icons.Default.ContentCopy,
                                                contentDescription = null,
                                                tint = MaterialTheme.colorScheme.onSurfaceVariant,
                                            )
                                            Text(
                                                text = codeState?.code ?: stringResource(R.string.two_fa_code_unavailable),
                                                style = MaterialTheme.typography.titleLarge,
                                                fontFamily = FontFamily.Monospace,
                                                textAlign = TextAlign.End,
                                            )
                                        }
                                    }
                                }
                            }
                            LinearProgressIndicator(
                                progress = { codeProgress },
                                modifier = Modifier.fillMaxWidth(),
                            )
                        }
                    }
                }
            }
        }
    }

    if (creating) {
        TwoFaTokenEditDialog(
            startValue = null,
            isBusy = uiState.isMutating,
            onDismiss = { creating = false },
            onSave = { issuer, account, secret, notes, digits, periodSeconds, algorithm ->
                viewModel.saveToken(
                    existingId = null,
                    issuer = issuer,
                    account = account,
                    secret = secret,
                    notes = notes,
                    digits = digits,
                    periodSeconds = periodSeconds,
                    algorithm = algorithm,
                )
                creating = false
            },
        )
    }

    editingToken?.let { token ->
        TwoFaTokenEditDialog(
            startValue = token,
            isBusy = uiState.isMutating,
            onDismiss = { editingToken = null },
            onSave = { issuer, account, secret, notes, digits, periodSeconds, algorithm ->
                viewModel.saveToken(
                    existingId = token.id,
                    issuer = issuer,
                    account = account,
                    secret = secret,
                    notes = notes,
                    digits = digits,
                    periodSeconds = periodSeconds,
                    algorithm = algorithm,
                )
                editingToken = null
            },
        )
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TwoFaTokenEditDialog(
    startValue: TwoFaTokenRecord?,
    isBusy: Boolean,
    onDismiss: () -> Unit,
    onSave: (String, String, String, String?, Int, Int, String) -> Unit,
) {
    val context = LocalContext.current
    var issuer by remember(startValue) { mutableStateOf(startValue?.issuer.orEmpty()) }
    var account by remember(startValue) { mutableStateOf(startValue?.account.orEmpty()) }
    var secret by remember(startValue) { mutableStateOf(startValue?.secret.orEmpty()) }
    var notes by remember(startValue) { mutableStateOf(startValue?.notes.orEmpty()) }
    var digitsValue by remember(startValue) { mutableIntStateOf((startValue?.digits ?: 6).coerceIn(6, 8)) }
    var periodSecondsValue by remember(startValue) { mutableIntStateOf((startValue?.periodSeconds ?: 30).coerceIn(10, 120)) }
    var algorithmValue by remember(startValue) { mutableStateOf((startValue?.algorithm ?: "SHA1").uppercase()) }
    var algorithmExpanded by remember { mutableStateOf(false) }
    var showScanner by remember { mutableStateOf(false) }
    var scanError by remember { mutableStateOf<String?>(null) }
    var permissionDenied by remember { mutableStateOf(false) }
    val dialogScrollState = rememberScrollState()
    var scrollContainerHeightPx by remember { mutableIntStateOf(0) }
    var scrollProgress by remember { mutableFloatStateOf(0f) }
    val density = LocalDensity.current
    val scanInvalidText = stringResource(R.string.two_fa_scan_qr_invalid)
    val cameraPermissionLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission(),
    ) { granted ->
        if (granted) {
            showScanner = true
            permissionDenied = false
        } else {
            permissionDenied = true
        }
    }
    LaunchedEffect(dialogScrollState) {
        snapshotFlow { dialogScrollState.value to dialogScrollState.maxValue }
            .collectLatest { (value, maxValue) ->
                scrollProgress = if (maxValue == 0) 0f else value.toFloat() / maxValue.toFloat()
            }
    }

    AlertDialog(
        onDismissRequest = { if (!isBusy) onDismiss() },
        title = {
            Text(
                if (startValue == null) {
                    stringResource(R.string.two_fa_create_title)
                } else {
                    stringResource(R.string.two_fa_edit_title)
                },
            )
        },
        text = {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .heightIn(max = 520.dp)
                    .onSizeChanged { scrollContainerHeightPx = it.height },
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(end = 10.dp)
                        .verticalScroll(dialogScrollState),
                    verticalArrangement = Arrangement.spacedBy(8.dp),
                ) {
                    if (isBusy) {
                        LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
                    }
                    OutlinedTextField(
                        value = issuer,
                        onValueChange = { issuer = it },
                        enabled = !isBusy,
                        label = { Text(stringResource(R.string.two_fa_field_issuer)) },
                    )
                    OutlinedTextField(
                        value = account,
                        onValueChange = { account = it },
                        enabled = !isBusy,
                        label = { Text(stringResource(R.string.two_fa_field_account)) },
                    )
                    OutlinedTextField(
                        value = secret,
                        onValueChange = { secret = it },
                        enabled = !isBusy,
                        label = { Text(stringResource(R.string.two_fa_field_secret)) },
                    )
                    Text(
                        text = stringResource(R.string.two_fa_field_algorithm),
                        style = MaterialTheme.typography.labelMedium,
                    )
                    ExposedDropdownMenuBox(
                        expanded = algorithmExpanded,
                        onExpandedChange = { if (!isBusy) algorithmExpanded = !algorithmExpanded },
                    ) {
                        val fillMaxWidth = Modifier
                            .fillMaxWidth()
                        OutlinedTextField(
                            value = algorithmValue,
                            onValueChange = {},
                            readOnly = true,
                            enabled = !isBusy,
                            modifier = fillMaxWidth.menuAnchor(
                                type = ExposedDropdownMenuAnchorType.PrimaryNotEditable,
                                enabled = !isBusy,
                            ),
                            trailingIcon = {
                                ExposedDropdownMenuDefaults.TrailingIcon(expanded = algorithmExpanded)
                            },
                        )
                        DropdownMenu(
                            expanded = algorithmExpanded,
                            onDismissRequest = { algorithmExpanded = false },
                        ) {
                            listOf("SHA1", "SHA256", "SHA512").forEach { algo ->
                                DropdownMenuItem(
                                    text = { Text(algo) },
                                    onClick = {
                                        algorithmValue = algo
                                        algorithmExpanded = false
                                    },
                                )
                            }
                        }
                    }
                    Text(
                        text = stringResource(R.string.two_fa_field_period_seconds_value, periodSecondsValue),
                        style = MaterialTheme.typography.labelMedium,
                    )
                    Slider(
                        value = periodSecondsValue.toFloat(),
                        onValueChange = { periodSecondsValue = it.roundToInt().coerceIn(10, 120) },
                        valueRange = 10f..120f,
                        steps = 109,
                        enabled = !isBusy,
                    )
                    Text(
                        text = stringResource(R.string.two_fa_field_digits_value, digitsValue),
                        style = MaterialTheme.typography.labelMedium,
                    )
                    Slider(
                        value = digitsValue.toFloat(),
                        onValueChange = { digitsValue = it.roundToInt().coerceIn(6, 8) },
                        valueRange = 6f..8f,
                        steps = 1,
                        enabled = !isBusy,
                    )
                    TextButton(
                        enabled = !isBusy,
                        onClick = {
                            val granted = ContextCompat.checkSelfPermission(
                                context,
                                Manifest.permission.CAMERA,
                            ) == PackageManager.PERMISSION_GRANTED
                            if (granted) {
                                showScanner = true
                                permissionDenied = false
                            } else {
                                cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
                            }
                        },
                    ) {
                        Icon(
                            imageVector = Icons.Default.QrCodeScanner,
                            contentDescription = null,
                            modifier = Modifier.padding(end = 8.dp),
                        )
                        Text(stringResource(R.string.two_fa_scan_qr_action))
                    }
                    if (permissionDenied) {
                        Text(
                            text = stringResource(R.string.two_fa_camera_permission_required),
                            color = Color.Red,
                            style = MaterialTheme.typography.bodySmall,
                        )
                    }
                    scanError?.let { error ->
                        Text(
                            text = error,
                            color = Color.Red,
                            style = MaterialTheme.typography.bodySmall,
                        )
                    }
                    OutlinedTextField(
                        value = notes,
                        onValueChange = { notes = it },
                        enabled = !isBusy,
                        label = { Text(stringResource(R.string.two_fa_field_notes_optional)) },
                    )
                }
                val trackColor = MaterialTheme.colorScheme.outlineVariant
                val thumbColor = MaterialTheme.colorScheme.primary
                Box(
                    modifier = Modifier
                        .align(Alignment.CenterEnd)
                        .fillMaxSize()
                        .padding(end = 2.dp, top = 4.dp, bottom = 4.dp),
                    contentAlignment = Alignment.TopEnd,
                ) {
                    Box(
                        modifier = Modifier
                            .fillMaxHeight()
                            .size(width = 3.dp, height = 0.dp)
                            .background(trackColor),
                    )
                    if (dialogScrollState.maxValue > 0 && scrollContainerHeightPx > 0) {
                        val viewport = scrollContainerHeightPx.toFloat()
                        val content = viewport + dialogScrollState.maxValue.toFloat()
                        val thumbHeightPx = (viewport * (viewport / content)).coerceAtLeast(24f)
                        val maxTravel = (viewport - thumbHeightPx).coerceAtLeast(0f)
                        val offsetY = maxTravel * scrollProgress
                        Box(
                            modifier = Modifier
                                .offset { IntOffset(0, offsetY.roundToInt()) }
                                .size(width = 3.dp, height = with(density) { thumbHeightPx.toDp() })
                                .background(thumbColor),
                        )
                    }
                }
            }
        },
        confirmButton = {
            TextButton(
                enabled = !isBusy && issuer.isNotBlank() && account.isNotBlank() && secret.isNotBlank(),
                onClick = {
                    onSave(
                        issuer,
                        account,
                        secret,
                        notes.ifBlank { null },
                        digitsValue,
                        periodSecondsValue,
                        algorithmValue,
                    )
                },
            ) {
                Text(stringResource(R.string.save))
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss, enabled = !isBusy) {
                Text(stringResource(R.string.cancel))
            }
        },
    )

    if (showScanner) {
        QrScannerDialog(
            onDismiss = { showScanner = false },
            onScanned = { raw ->
                val parsed = parseOtpAuthTotpUri(raw)
                if (parsed == null) {
                    scanError = scanInvalidText
                    return@QrScannerDialog
                }
                issuer = parsed.issuer
                account = parsed.account
                secret = parsed.secret
                digitsValue = parsed.digits.coerceIn(6, 8)
                periodSecondsValue = parsed.periodSeconds.coerceIn(10, 120)
                algorithmValue = parsed.algorithm.uppercase()
                scanError = null
                showScanner = false
            },
        )
    }
}

@Composable
private fun rememberTwoFaCode(token: TwoFaTokenRecord, nowMillis: Long): TwoFaCodeState? {
    val period = token.periodSeconds.coerceAtLeast(1)
    val periodSlot = nowMillis / 1000L / period
    return remember(token, periodSlot) {
        buildTwoFaCodeState(token, nowMillis)
    }
}
