package net.hivetechnology.o20010_06

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.net.VpnService
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.animation.core.LinearEasing
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.border
import androidx.compose.foundation.isSystemInDarkTheme
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.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.outlined.OpenInBrowser
import androidx.compose.material.icons.outlined.StopCircle
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.hivetechnology.o20010_06.vpn.NativeBridge

class MainActivity : ComponentActivity() {
    private val viewModel: MainViewModel by viewModels()
    private val vpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        if (it.resultCode == RESULT_OK) {
            waitForVpnAuthorization()
        } else {
            viewModel.permissionHandled(false)
        }
    }
    private val notificationPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
        prepareVpnPermission()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { App(viewModel, ::requestVpnPermission, ::openPortal) }
        handleIntent(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
        if (BuildConfig.DEBUG) {
            intent?.getStringExtra(EXTRA_STORE_PREVIEW)?.let {
                viewModel.showStorePreview(it)
                return
            }
        }
        val returnUrl = validatedMobileVpnPortalUrl(intent?.data?.getQueryParameter("return"))
        val ticket = intent?.data?.fragment
            ?.split('&')
            ?.mapNotNull { part -> part.split('=', limit = 2).takeIf { it.size == 2 } }
            ?.firstOrNull { it[0] == "handoff" }
            ?.get(1)
        if (!ticket.isNullOrBlank()) {
            viewModel.acceptHandoff(ticket, returnUrl)
            intent.data = null
        }
    }

    private fun openPortal() {
        val lightColors = CustomTabColorSchemeParams.Builder()
            .setToolbarColor(getColor(android.R.color.white))
            .setNavigationBarColor(getColor(android.R.color.white))
            .setNavigationBarDividerColor(getColor(R.color.brand_blue))
            .build()
        val darkColors = CustomTabColorSchemeParams.Builder()
            .setToolbarColor(getColor(R.color.brand_background))
            .setNavigationBarColor(getColor(R.color.brand_background))
            .setNavigationBarDividerColor(getColor(R.color.brand_blue))
            .build()
        val customTab = CustomTabsIntent.Builder()
            .setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_LIGHT, lightColors)
            .setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, darkColors)
            .setShowTitle(true)
            .setShareState(CustomTabsIntent.SHARE_STATE_OFF)
            .setUrlBarHidingEnabled(false)
            .build()
        runCatching {
            customTab.launchUrl(this, Uri.parse(viewModel.portalDestination()))
        }.onFailure {
            viewModel.portalOpenFailed()
        }
    }

    private fun requestVpnPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
            ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
        ) {
            notificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
            return
        }
        prepareVpnPermission()
    }

    private fun prepareVpnPermission() {
        val preparation = VpnService.prepare(this)
        if (preparation == null) viewModel.permissionHandled(true) else vpnPermission.launch(preparation)
    }

    private fun waitForVpnAuthorization() {
        lifecycleScope.launch {
            repeat(VPN_AUTHORIZATION_CHECKS) {
                if (VpnService.prepare(this@MainActivity) == null) {
                    viewModel.permissionHandled(true)
                    return@launch
                }
                delay(VPN_AUTHORIZATION_CHECK_INTERVAL_MS)
            }
            viewModel.vpnAuthorizationUnavailable()
        }
    }

    private companion object {
        const val EXTRA_STORE_PREVIEW = "store-preview"
        const val VPN_AUTHORIZATION_CHECKS = 25
        const val VPN_AUTHORIZATION_CHECK_INTERVAL_MS = 200L
    }
}

@Composable
private fun App(
    viewModel: MainViewModel,
    requestPermission: () -> Unit,
    openPortal: () -> Unit,
) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val dark = isSystemInDarkTheme()
    val colors = if (dark) darkColorScheme(
        primary = Color(0xFF5BB4E6),
        surface = Color(0xFF1F2022),
        background = Color(0xFF151617),
    ) else lightColorScheme(
        primary = Color(0xFF137CC1),
        surface = Color.White,
        background = Color(0xFFF2F3F4),
    )

    MaterialTheme(colorScheme = colors) {
        Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
            Column(modifier = Modifier.fillMaxSize()) {
                AppHeader()
                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .background(MaterialTheme.colorScheme.surface)
                        .verticalScroll(rememberScrollState())
                        .navigationBarsPadding()
                        .padding(horizontal = 20.dp, vertical = 28.dp),
                    horizontalAlignment = Alignment.CenterHorizontally,
                ) {
                    StatusPanel(
                        state,
                        viewModel::acceptVpnDisclosure,
                        viewModel::cancelVpnDisclosure,
                        openPortal,
                        viewModel::disconnect,
                    )
                }
            }
        }
    }
    LaunchedEffect(state.needsPermission) {
        if (state.needsPermission) requestPermission()
    }
}

@Composable
private fun AppHeader() {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .background(MaterialTheme.colorScheme.surface)
            .statusBarsPadding()
            .padding(horizontal = 20.dp, vertical = 14.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(12.dp),
    ) {
        Icon(
            painter = painterResource(R.drawable.ic_brand_mark),
            contentDescription = null,
            tint = Color.Unspecified,
            modifier = Modifier.size(40.dp),
        )
        Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
            Text(stringResource(R.string.app_name), fontWeight = FontWeight.SemiBold)
            Text(
                stringResource(R.string.app_subtitle),
                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
            )
        }
    }
    HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.55f))
}

@Composable
private fun StatusPanel(
    state: MainUiState,
    acceptVpnDisclosure: () -> Unit,
    cancelVpnDisclosure: () -> Unit,
    openPortal: () -> Unit,
    disconnect: () -> Unit,
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .widthIn(max = 500.dp)
            .padding(horizontal = 6.dp, vertical = 4.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        ConnectionSignal(state)
        Spacer(Modifier.height(20.dp))
        Text(
            stringResource(R.string.protected_handoff).uppercase(),
            style = MaterialTheme.typography.labelSmall,
            color = MaterialTheme.colorScheme.primary,
            fontWeight = FontWeight.Bold,
        )
        Spacer(Modifier.height(8.dp))
        Text(
            state.title,
            style = MaterialTheme.typography.headlineSmall,
            fontWeight = FontWeight.SemiBold,
            textAlign = TextAlign.Center,
        )
        if (state.deviceName.isNotBlank()) {
            Spacer(Modifier.height(7.dp))
            Text(state.deviceName, color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.SemiBold)
        }
        Spacer(Modifier.height(11.dp))
        Text(
            state.error ?: state.detail,
            style = MaterialTheme.typography.bodyMedium,
            color = if (state.error == null) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.error,
            textAlign = TextAlign.Center,
        )
        if (state.phase == "connected" && !state.transport.isNullOrBlank()) {
            Spacer(Modifier.height(14.dp))
            TransportStatus(state.transport)
        }

        Spacer(Modifier.height(18.dp))

        if (state.needsDisclosure) {
            Button(onClick = acceptVpnDisclosure, modifier = Modifier.fillMaxWidth()) {
                Text(stringResource(R.string.continue_and_authorize))
            }
            Spacer(Modifier.height(10.dp))
            OutlinedButton(onClick = cancelVpnDisclosure, modifier = Modifier.fillMaxWidth()) {
                Text(stringResource(R.string.cancel))
            }
        } else if (shouldShowPortalAction(state.phase)) {
            Button(onClick = openPortal, modifier = Modifier.fillMaxWidth()) {
                Icon(Icons.Outlined.OpenInBrowser, contentDescription = null)
                Spacer(Modifier.size(ButtonDefaults.IconSpacing))
                Text(stringResource(R.string.sign_in_select_device))
            }
        }

        if (state.phase in setOf("connected", "connecting", "reconnecting", "waiting-runtime", "awaiting-permission")) {
            Spacer(Modifier.height(10.dp))
            OutlinedButton(
                onClick = disconnect,
                modifier = Modifier.fillMaxWidth(),
                colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
            ) {
                Icon(Icons.Outlined.StopCircle, contentDescription = null)
                Spacer(Modifier.size(ButtonDefaults.IconSpacing))
                Text(stringResource(R.string.disconnect))
            }
        }

        Spacer(Modifier.height(20.dp))
        Text(
            stringResource(
                R.string.version_format,
                BuildConfig.VERSION_NAME,
                runCatching { NativeBridge.version() }.getOrDefault("3.4.6"),
            ),
            style = MaterialTheme.typography.labelSmall,
            color = MaterialTheme.colorScheme.outline,
        )
        OpenSourceLicenses()
    }
}

private enum class LegalDocument(val assetName: String) {
    NOTICES("THIRD_PARTY_NOTICES.md"),
    GPL("GPL-3.0-only.txt"),
    LGPL("LGPL-2.1-only.txt"),
}

@Composable
private fun OpenSourceLicenses() {
    val context = LocalContext.current
    var visible by remember { mutableStateOf(false) }
    var document by remember { mutableStateOf(LegalDocument.NOTICES) }

    TextButton(onClick = { visible = true }) {
        Text(stringResource(R.string.open_source_licenses))
    }

    if (!visible) return

    val contents = remember(document) {
        runCatching {
            context.assets.open("legal/${document.assetName}").bufferedReader().use { it.readText() }
        }.getOrElse { context.getString(R.string.license_read_error) }
    }

    AlertDialog(
        onDismissRequest = { visible = false },
        title = { Text(stringResource(R.string.open_source_licenses)) },
        text = {
            Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.spacedBy(4.dp),
                ) {
                    LegalDocument.entries.forEach { item ->
                        val label = when (item) {
                            LegalDocument.NOTICES -> stringResource(R.string.license_notices)
                            LegalDocument.GPL -> "GPL 3.0"
                            LegalDocument.LGPL -> "LGPL 2.1"
                        }
                        TextButton(
                            onClick = { document = item },
                            modifier = Modifier.weight(1f),
                            colors = ButtonDefaults.textButtonColors(
                                contentColor = if (document == item) {
                                    MaterialTheme.colorScheme.primary
                                } else {
                                    MaterialTheme.colorScheme.onSurfaceVariant
                                },
                            ),
                        ) {
                            Text(label, textAlign = TextAlign.Center)
                        }
                    }
                }
                SelectionContainer {
                    Text(
                        contents,
                        modifier = Modifier
                            .heightIn(max = 360.dp)
                            .verticalScroll(rememberScrollState()),
                        style = MaterialTheme.typography.bodySmall,
                    )
                }
            }
        },
        confirmButton = {
            TextButton(
                onClick = {
                    context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(BuildConfig.GPL_SOURCE_URL)))
                },
            ) {
                Icon(Icons.Outlined.OpenInBrowser, contentDescription = null)
                Spacer(Modifier.size(ButtonDefaults.IconSpacing))
                Text(stringResource(R.string.source_code))
            }
        },
        dismissButton = {
            TextButton(onClick = { visible = false }) {
                Text(stringResource(R.string.close))
            }
        },
    )
}

@Composable
private fun TransportStatus(transport: String?) {
    val label = when (transport) {
        "udp-relay" -> stringResource(R.string.transport_udp_relay)
        "tcp-relay" -> stringResource(R.string.transport_tcp_relay)
        else -> stringResource(R.string.transport_udp_p2p)
    }
    Column(
        modifier = Modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        Text(
            stringResource(R.string.transport_format, label),
            style = MaterialTheme.typography.labelMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
        if (transport == "udp-relay" || transport == "tcp-relay") {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .background(Color(0x1AF7981D), RoundedCornerShape(6.dp))
                    .padding(horizontal = 12.dp, vertical = 10.dp),
                horizontalArrangement = Arrangement.spacedBy(9.dp),
                verticalAlignment = Alignment.Top,
            ) {
                Icon(
                    Icons.Outlined.WarningAmber,
                    contentDescription = null,
                    tint = Color(0xFFD97706),
                    modifier = Modifier.size(18.dp),
                )
                Text(
                    stringResource(
                        if (transport == "udp-relay") {
                            R.string.transport_udp_relay_warning
                        } else {
                            R.string.transport_tcp_relay_warning
                        },
                    ),
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurface,
                )
            }
        }
    }
}

@Composable
private fun ConnectionSignal(state: MainUiState) {
    Box(modifier = Modifier.size(144.dp), contentAlignment = Alignment.Center) {
        OrbitRing(136.dp, 4_800)
        OrbitRing(100.dp, 3_600, clockwise = false)
        Box(
            modifier = Modifier
                .size(76.dp)
                .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f), RoundedCornerShape(8.dp)),
            contentAlignment = Alignment.Center,
        ) {
            Icon(
                painter = painterResource(R.drawable.ic_brand_mark),
                contentDescription = null,
                tint = Color.Unspecified,
                modifier = Modifier.size(54.dp),
            )
        }
        when {
            state.loading -> Box(
                modifier = Modifier
                    .align(Alignment.BottomEnd)
                    .size(28.dp)
                    .background(MaterialTheme.colorScheme.surface, CircleShape)
                    .border(1.dp, MaterialTheme.colorScheme.outlineVariant, CircleShape),
                contentAlignment = Alignment.Center,
            ) {
                CircularProgressIndicator(
                    modifier = Modifier.size(16.dp),
                    strokeWidth = 2.dp,
                )
            }
            state.phase == "connected" -> Icon(
                Icons.Outlined.CheckCircle,
                contentDescription = null,
                tint = Color(0xFF25A56A),
                modifier = Modifier
                    .align(Alignment.BottomEnd)
                    .size(28.dp)
                    .background(MaterialTheme.colorScheme.surface, CircleShape)
                    .padding(2.dp),
            )
            state.error != null || state.phase == "failed" -> Icon(
                Icons.Outlined.ErrorOutline,
                contentDescription = null,
                tint = MaterialTheme.colorScheme.error,
                modifier = Modifier
                    .align(Alignment.BottomEnd)
                    .size(28.dp)
                    .background(MaterialTheme.colorScheme.surface, CircleShape)
                    .padding(2.dp),
            )
            else -> Unit
        }
    }
}

@Composable
private fun OrbitRing(diameter: Dp, durationMillis: Int, clockwise: Boolean = true) {
    val transition = rememberInfiniteTransition(label = "vpn-orbit")
    val rotation by transition.animateFloat(
        initialValue = 0f,
        targetValue = if (clockwise) 360f else -360f,
        animationSpec = infiniteRepeatable(animation = tween(durationMillis, easing = LinearEasing)),
        label = "vpn-orbit-angle",
    )
    Box(
        modifier = Modifier
            .size(diameter)
            .rotate(rotation)
            .border(1.dp, MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.8f), CircleShape),
    ) {
        Box(
            Modifier
                .align(Alignment.CenterStart)
                .offset(x = (-4.5).dp)
                .size(9.dp)
                .background(Color(0xFFF7981D), CircleShape),
        )
    }
}
