package wang.yaojia.webterm

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import wang.yaojia.webterm.designsystem.WebTermTheme
import wang.yaojia.webterm.hostregistry.Host
import wang.yaojia.webterm.nav.DeepLinkRouter
import wang.yaojia.webterm.nav.NavRoutes
import wang.yaojia.webterm.nav.WebTermNavHost
import wang.yaojia.webterm.push.PushCoordinator
import wang.yaojia.webterm.wiring.AppEnvironment
import javax.inject.Inject

/**
 * The single launcher Activity and composition root. A `@AndroidEntryPoint` [FragmentActivity]
 * (FragmentActivity so the biometric/allow trampoline hierarchy is consistent) that:
 *
 *  - **warms the mTLS/OkHttp stack off `Main`** ([AppEnvironment.warmUp]) before any terminal bind;
 *  - **registers this device's FCM token with every paired host on app start** ([PushCoordinator]);
 *  - **hands the launch/new intents to [DeepLinkRouter]** (the ONE whitelist parser) and navigates only a
 *    validated [NavRoutes.forDeepLink] route — the manifest `<intent-filter>`s deliver, never validate;
 *  - renders [WebTermNavHost] with the cold-start start destination ([AppEnvironment.coldStartPolicy]).
 */
@AndroidEntryPoint
public class MainActivity : FragmentActivity() {

    @Inject
    public lateinit var appEnvironment: AppEnvironment

    @Inject
    public lateinit var pushCoordinator: PushCoordinator

    /** The latest deep-link URI to route (null = nothing pending). Fed by onCreate + onNewIntent. */
    private val pendingDeepLink = MutableStateFlow<String?>(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Build the shared OkHttp/mTLS stack OFF-Main before the first bind (warmUp hops to IO itself);
        // a real failure is swallowed (the terminal screen surfaces its own retry, FIX 5) but cancellation
        // is rethrown so a destroyed Activity tears the warm-up down cleanly.
        lifecycleScope.launch {
            try {
                appEnvironment.warmUp()
            } catch (cancel: CancellationException) {
                throw cancel
            } catch (_: Throwable) {
                // best-effort pre-warm; TerminalScreen re-runs warmUp with an actionable retry.
            }
        }
        // App start: register the current FCM token with every paired host (best-effort self-heal).
        pushCoordinator.registerAllHosts()

        handleDeepLinkIntent(intent)

        setContent {
            WebTermTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    val navController = rememberNavController()
                    NotificationPermissionGate()
                    ColdStartHost(
                        env = appEnvironment,
                        navController = navController,
                        pendingDeepLink = pendingDeepLink,
                        onHostPaired = { host -> pushCoordinator.registerHost(host) },
                    )
                }
            }
        }
    }

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

    /** Capture a VIEW deep link's data URI; a launcher/push-body intent has none → nothing to route. */
    private fun handleDeepLinkIntent(intent: Intent?) {
        pendingDeepLink.value = intent?.data?.toString()
    }
}

/**
 * Resolve the cold-start destination ([AppEnvironment.coldStartPolicy]) then render the graph. Shows a
 * spinner until the (suspend) host-presence read completes.
 */
@Composable
private fun ColdStartHost(
    env: AppEnvironment,
    navController: NavHostController,
    pendingDeepLink: MutableStateFlow<String?>,
    onHostPaired: (Host) -> Unit,
) {
    val startRoute by produceState<String?>(initialValue = null, key1 = env) {
        value = NavRoutes.startRouteFor(env.coldStartPolicy.initialRoute())
    }
    val route = startRoute
    if (route == null) {
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            CircularProgressIndicator()
        }
    } else {
        WebTermNavHost(
            env = env,
            startRoute = route,
            navController = navController,
            onHostPaired = onHostPaired,
        )
        // Route pending deep links ONLY here — inside the `route != null` branch, so `WebTermNavHost`
        // (which calls navController.setGraph) has composed and the graph is set before the effect body
        // runs. On a cold launch the VIEW intent is captured before setContent, but its navigation is
        // deferred to this point instead of racing an unset graph (which silently dropped the link).
        DeepLinkEffect(pending = pendingDeepLink, navController = navController)
    }
}

/**
 * Route a pending deep-link URI through [DeepLinkRouter] (the ONE whitelist parser) and navigate only a
 * validated route; invalid/ambiguous links are ignored (never partially applied). Clears the pending
 * value once handled.
 */
@Composable
private fun DeepLinkEffect(
    pending: MutableStateFlow<String?>,
    navController: NavHostController,
) {
    val uri by pending.collectAsStateWithLifecycle()
    LaunchedEffect(uri) {
        val target = uri ?: return@LaunchedEffect
        NavRoutes.forDeepLink(DeepLinkRouter.route(target))?.let { route ->
            runCatching { navController.navigate(route) }
        }
        pending.value = null
    }
}

/** Request POST_NOTIFICATIONS once on first launch (API 33+); a denial only stops push from being SHOWN. */
@Composable
private fun NotificationPermissionGate() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
    val context = LocalContext.current
    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { }
    LaunchedEffect(Unit) {
        val granted = ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) ==
            PackageManager.PERMISSION_GRANTED
        if (!granted) launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
    }
}
