package chat.vojo.proxy.ui

import android.annotation.SuppressLint
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
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.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalLifecycleOwner
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.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import chat.vojo.proxy.core.AppSettings
import chat.vojo.proxy.core.ProxyConfig
import chat.vojo.proxy.core.ProxyType
import chat.vojo.proxy.core.formatBytes
import chat.vojo.proxy.core.formatClock
import chat.vojo.proxy.core.formatUptime
import chat.vojo.proxy.service.VpnState
import chat.vojo.proxy.ui.theme.Vojo
import kotlinx.coroutines.delay
import java.util.Calendar

private fun protoColor(config: ProxyConfig?): Color = when (config?.type) {
    ProxyType.SHADOWSOCKS -> Vojo.fleet
    ProxyType.SOCKS5 -> Vojo.blue
    ProxyType.HTTP -> Vojo.amber
    ProxyType.HTTPS -> Vojo.teal
    null -> Vojo.fleetSoft
}

/** The Dawn hour palette: night → dawn → noon → dusk, colour lives on the time axis. */
private fun hourColor(h: Int): Color = when {
    h < 5 -> Color(0xFF3A3D57)
    h < 7 -> Color(0xFF4A4A6B)
    h < 10 -> Color(0xFFC39A7A)
    h < 13 -> Color(0xFFD4B88A)
    h < 16 -> Color(0xFFA8B39A)
    h < 19 -> Color(0xFFB8A187)
    h < 21 -> Color(0xFFC08E7B)
    h < 23 -> Color(0xFF6E6587)
    else -> Color(0xFF3A3D57)
}

private fun eventHour(at: Long): Int =
    Calendar.getInstance().apply { timeInMillis = at }.get(Calendar.HOUR_OF_DAY)

@Composable
fun ConnectionScreen(
    activeConfig: ProxyConfig?,
    configs: List<ProxyConfig>,
    status: VpnState.Status,
    stats: VpnState.Stats,
    connectedSince: Long,
    error: String?,
    events: List<VpnState.TunnelEvent>,
    applied: VpnState.Applied?,
    settings: AppSettings,
    liveId: String?,
    onConnect: () -> Unit,
    onDisconnect: () -> Unit,
    onReconnect: () -> Unit,
    onSelectServer: (String) -> Unit,
) {
    var pickerOpen by remember { mutableStateOf(false) }
    val connected = status == VpnState.Status.CONNECTED
    val active = status == VpnState.Status.CONNECTING || status == VpnState.Status.CONNECTED || status == VpnState.Status.STOPPING
    val live = activeConfig != null && activeConfig.id == liveId

    Column(Modifier.fillMaxSize()) {
        // ── Fixed header — never reflows on connect/disconnect ──
        Column(Modifier.fillMaxWidth().padding(horizontal = 18.dp, vertical = 16.dp)) {
            if (error != null && status == VpnState.Status.ERROR) {
                ErrorBanner(error)
                Spacer(Modifier.height(14.dp))
            }

            ActiveServerCard(
                config = activeConfig,
                live = live,
                active = active,
                onPick = if (configs.size > 1 && !active) ({ pickerOpen = true }) else null,
                onDisconnect = onDisconnect,
            )

            val stale = connected && applied != null &&
                (applied.configId != activeConfig?.id || applied.settings != settings)
            if (stale) {
                Spacer(Modifier.height(10.dp))
                PanelCard(
                    Modifier.fillMaxWidth(),
                    onClick = onReconnect,
                    accent = Vojo.amber.copy(alpha = 0.45f),
                ) {
                    Row(Modifier.padding(horizontal = 16.dp, vertical = 13.dp), verticalAlignment = Alignment.CenterVertically) {
                        Text("Настройки изменены", color = Vojo.muted, fontSize = 13.sp, modifier = Modifier.weight(1f))
                        Text("переподключить", color = Vojo.fleetSoft, fontSize = 13.sp)
                    }
                }
            }

            Spacer(Modifier.height(16.dp))

            // Fixed-height slot: button when down, stats when up — swapping in
            // place means the journal below never jumps on connect/disconnect.
            Box(Modifier.fillMaxWidth().height(72.dp), contentAlignment = Alignment.Center) {
                when {
                    connected -> StatsRow(stats, connectedSince)
                    status == VpnState.Status.CONNECTING || status == VpnState.Status.STOPPING ->
                        ConnectingButton(status)
                    else -> ConnectButton(status, onConnect, enabled = activeConfig != null)
                }
            }
        }

        // ── Scrollable journal — the only thing that scrolls; autoscrolls down ──
        Column(Modifier.fillMaxWidth().weight(1f).padding(horizontal = 18.dp)) {
            SectionLabel("Журнал", Modifier.padding(start = 2.dp, bottom = 10.dp))
            JournalList(
                events = events,
                pulseLast = status == VpnState.Status.CONNECTING,
                modifier = Modifier.fillMaxWidth().weight(1f),
            )
        }
    }

    if (pickerOpen) {
        ServerPickerSheet(
            configs = configs,
            activeId = activeConfig?.id,
            liveId = liveId,
            onSelect = { onSelectServer(it); pickerOpen = false },
            onDismiss = { pickerOpen = false },
        )
    }
}

@Composable
private fun ErrorBanner(message: String) {
    PanelCard(
        Modifier.fillMaxWidth(),
        accent = Vojo.danger.copy(alpha = 0.5f),
        background = Vojo.danger.copy(alpha = 0.08f),
    ) {
        Row(Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
            Box(Modifier.size(8.dp).clip(CircleShape).background(Vojo.danger))
            Spacer(Modifier.width(12.dp))
            Text(message, color = Vojo.danger, fontSize = 14.sp, modifier = Modifier.weight(1f))
        }
    }
}

@Composable
private fun ActiveServerCard(
    config: ProxyConfig?,
    live: Boolean,
    active: Boolean,
    onPick: (() -> Unit)?,
    onDisconnect: () -> Unit,
) {
    PanelCard(
        Modifier.fillMaxWidth(),
        onClick = onPick,
        accent = if (live) protoColor(config).copy(alpha = 0.45f) else null,
    ) {
        Row(Modifier.padding(14.dp), verticalAlignment = Alignment.CenterVertically) {
            ProtocolBadge(config?.type?.badge ?: "—", protoColor(config), size = 48)
            Spacer(Modifier.width(14.dp))
            Column(Modifier.weight(1f)) {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Text(
                        config?.name ?: "Сервер не выбран",
                        color = if (live) Vojo.green else Vojo.text,
                        fontSize = 17.sp, fontWeight = FontWeight.SemiBold,
                        maxLines = 1, overflow = TextOverflow.Ellipsis,
                        modifier = Modifier.weight(1f, fill = false),
                    )
                    if (live) {
                        Spacer(Modifier.width(8.dp))
                        OnlineDot(Vojo.green, 7)
                    }
                }
                Spacer(Modifier.height(3.dp))
                Text(
                    config?.handle ?: "выберите на вкладке «Серверы»",
                    color = Vojo.muted, fontFamily = Vojo.mono, fontSize = 13.sp,
                    maxLines = 1, overflow = TextOverflow.Ellipsis,
                )
            }
            if (active) {
                // Disconnect lives inside the card as a square ✕.
                Spacer(Modifier.width(10.dp))
                Box(
                    Modifier
                        .size(46.dp)
                        .clip(RoundedCornerShape(12.dp))
                        .background(Vojo.danger.copy(alpha = 0.14f))
                        .clickable { onDisconnect() },
                    contentAlignment = Alignment.Center,
                ) {
                    Icon(VojoIcons.Cross, contentDescription = "Отключить", tint = Vojo.danger, modifier = Modifier.size(18.dp))
                }
            } else if (config != null && onPick != null) {
                Spacer(Modifier.width(8.dp))
                Text("сменить", color = Vojo.fleetSoft, fontSize = 13.sp)
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ServerPickerSheet(
    configs: List<ProxyConfig>,
    activeId: String?,
    liveId: String?,
    onSelect: (String) -> Unit,
    onDismiss: () -> Unit,
) {
    ModalBottomSheet(
        onDismissRequest = onDismiss,
        containerColor = Vojo.bgPanel,
        contentColor = Vojo.text,
        dragHandle = {
            Box(
                Modifier
                    .padding(top = 12.dp, bottom = 6.dp)
                    .size(width = 38.dp, height = 4.dp)
                    .clip(RoundedCornerShape(2.dp))
                    .background(Vojo.hairline)
            )
        },
    ) {
        Column(Modifier.padding(horizontal = 14.dp, vertical = 4.dp)) {
            SectionLabel("Сменить сервер", Modifier.padding(start = 4.dp, bottom = 10.dp))
            configs.forEach { cfg ->
                val isActive = cfg.id == activeId
                val isLive = cfg.id == liveId
                Row(
                    Modifier
                        .fillMaxWidth()
                        .clip(RoundedCornerShape(12.dp))
                        .background(if (isActive) Vojo.surface else Color.Transparent)
                        .clickable { onSelect(cfg.id) }
                        .padding(horizontal = 12.dp, vertical = 12.dp),
                    verticalAlignment = Alignment.CenterVertically,
                ) {
                    ProtocolBadge(cfg.type.badge, protoColor(cfg), size = 38)
                    Spacer(Modifier.width(12.dp))
                    Column(Modifier.weight(1f)) {
                        Text(
                            cfg.name,
                            color = if (isLive) Vojo.green else Vojo.text,
                            fontSize = 15.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis,
                        )
                        Text(cfg.handle, color = Vojo.muted, fontFamily = Vojo.mono, fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
                    }
                    if (isLive) OnlineDot(Vojo.green, 7)
                    else if (isActive) OnlineDot(Vojo.fleetSoft, 7)
                }
            }
            Spacer(Modifier.height(20.dp))
        }
    }
}

@Composable
private fun ConnectButton(status: VpnState.Status, onConnect: () -> Unit, enabled: Boolean) {
    val bg = if (enabled) Vojo.fleet else Vojo.surface
    val fg = if (enabled) Color(0xFF0C0C0E) else Vojo.faint
    Box(
        Modifier
            .fillMaxWidth()
            .height(58.dp)
            .clip(RoundedCornerShape(15.dp))
            .background(bg)
            .clickable(enabled = enabled) { onConnect() },
        contentAlignment = Alignment.Center,
    ) {
        Text("Подключиться", color = fg, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
    }
}

/** Non-interactive transition state shown while the tunnel comes up or tears down. */
@Composable
private fun ConnectingButton(status: VpnState.Status) {
    val label = if (status == VpnState.Status.STOPPING) "Отключение…" else "Подключение…"
    Box(
        Modifier
            .fillMaxWidth()
            .height(58.dp)
            .clip(RoundedCornerShape(15.dp))
            .background(Vojo.surface),
        contentAlignment = Alignment.Center,
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            CircularProgressIndicator(color = Vojo.fleetSoft, strokeWidth = 2.dp, modifier = Modifier.size(16.dp))
            Spacer(Modifier.width(10.dp))
            Text(label, color = Vojo.muted, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
        }
    }
}

@Composable
private fun StatsRow(stats: VpnState.Stats, connectedSince: Long) {
    // Only this leaf subscribes to the 1 Hz tick, so the rest of the screen (and
    // the pager) doesn't recompose every second while connected.
    val nowMs = rememberNowTicker()
    Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
        StatTile("Отправлено", formatBytes(stats.txBytes), Modifier.weight(1f))
        StatTile("Получено", formatBytes(stats.rxBytes), Modifier.weight(1f))
        StatTile("Время", formatUptime(connectedSince, nowMs), Modifier.weight(1f))
    }
}

@SuppressLint("ProduceStateDoesNotAssignValue") // false positive: value is assigned inside repeatOnLifecycle
@Composable
private fun rememberNowTicker(): Long {
    val lifecycle = LocalLifecycleOwner.current.lifecycle
    val now by produceState(initialValue = System.currentTimeMillis(), lifecycle) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
            while (true) {
                value = System.currentTimeMillis()
                delay(1000)
            }
        }
    }
    return now
}

@Composable
private fun StatTile(label: String, value: String, modifier: Modifier = Modifier) {
    PanelCard(modifier) {
        Column(Modifier.padding(14.dp)) {
            Text(label, color = Vojo.faint, fontSize = 12.sp)
            Spacer(Modifier.height(5.dp))
            Text(value, color = Vojo.text, fontFamily = Vojo.mono, fontSize = 17.sp, fontWeight = FontWeight.SemiBold, maxLines = 1)
        }
    }
}

/** Connection journal: a scrollable, auto-scrolling timeline rail of events. */
@Composable
private fun JournalList(
    events: List<VpnState.TunnelEvent>,
    pulseLast: Boolean,
    modifier: Modifier = Modifier,
) {
    if (events.isEmpty()) {
        Row(modifier) {
            Text(
                "—",
                Modifier.width(50.dp).padding(end = 12.dp),
                color = Vojo.faint, fontFamily = Vojo.mono, fontSize = 11.sp, textAlign = TextAlign.End,
            )
            Text("Готов к подключению", color = Vojo.muted, fontSize = 14.sp)
        }
        return
    }
    val listState = rememberLazyListState()
    // Auto-scroll to the newest event as it lands. Keyed on the last event's id,
    // NOT events.size — the ring buffer saturates at MAX_EVENTS, so size stops
    // changing while new events keep arriving.
    LaunchedEffect(events.lastOrNull()?.id) {
        if (events.isNotEmpty()) listState.animateScrollToItem(events.lastIndex)
    }
    LazyColumn(modifier, state = listState) {
        itemsIndexed(events, key = { _, e -> e.id }) { i, e ->
            val color = when (e.tone) {
                VpnState.TunnelEvent.Tone.OK -> Vojo.green
                VpnState.TunnelEvent.Tone.WARN -> Vojo.amber
                VpnState.TunnelEvent.Tone.ERROR -> Vojo.danger
                VpnState.TunnelEvent.Tone.NEUTRAL -> hourColor(eventHour(e.at))
            }
            RailRow(
                time = formatClock(e.at),
                label = e.label,
                sub = e.detail,
                color = color,
                error = e.tone == VpnState.TunnelEvent.Tone.ERROR,
                drawTop = i > 0,
                drawBottom = i < events.lastIndex,
                pulse = pulseLast && i == events.lastIndex,
            )
        }
    }
}

@Composable
private fun RailRow(
    time: String,
    label: String,
    sub: String?,
    color: Color,
    error: Boolean,
    drawTop: Boolean,
    drawBottom: Boolean,
    pulse: Boolean,
) {
    Row(Modifier.fillMaxWidth().height(if (sub.isNullOrBlank()) 42.dp else 50.dp)) {
        Text(
            time,
            Modifier.width(50.dp).padding(end = 12.dp, top = 3.dp),
            color = Vojo.faint, fontFamily = Vojo.mono, fontSize = 11.sp,
            textAlign = TextAlign.End, maxLines = 1,
        )
        Box(Modifier.width(16.dp).fillMaxHeight()) {
            // The rail line and the dot must share the same centre. The Column
            // fills the cell WIDTH so CenterHorizontally actually centres its 1dp
            // children; with only fillMaxHeight it shrink-wraps to 1dp and pins to
            // the cell's start, leaving the dot floating ~7dp to its right.
            Column(
                Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                Box(Modifier.width(1.dp).height(9.dp).background(if (drawTop) Vojo.rail else Color.Transparent))
                Box(Modifier.weight(1f).width(1.dp).background(if (drawBottom) Vojo.rail else Color.Transparent))
            }
            RailDot(color, pulse, Modifier.align(Alignment.TopCenter).padding(top = 2.dp))
        }
        Spacer(Modifier.width(14.dp))
        Column {
            Text(
                label,
                color = if (error) Vojo.danger else Vojo.text,
                fontSize = 14.5.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis,
            )
            if (!sub.isNullOrBlank()) {
                Text(sub, color = Vojo.muted, fontFamily = Vojo.mono, fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
            }
        }
    }
}

@Composable
private fun RailDot(color: Color, pulse: Boolean, modifier: Modifier = Modifier) {
    Box(
        modifier.size(15.dp).clip(CircleShape).background(Vojo.bgApp),
        contentAlignment = Alignment.Center,
    ) {
        var dot = Modifier.size(9.dp)
        if (pulse) {
            val t = rememberInfiniteTransition(label = "rail-pulse")
            val scale by t.animateFloat(
                initialValue = 0.85f, targetValue = 1.2f,
                animationSpec = infiniteRepeatable(tween(900, easing = LinearEasing), RepeatMode.Reverse),
                label = "dot",
            )
            dot = dot.graphicsLayer { scaleX = scale; scaleY = scale }
        }
        Box(dot.clip(CircleShape).background(color))
    }
}
