/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app

import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.lifecycle.lifecycleScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.runicgateway.app.core.auth.sso.SsoAuthManager
import com.runicgateway.app.core.push.PushNotifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.runicgateway.app.ui.AppViewModel
import com.runicgateway.app.ui.AppViewModel.AppState
import com.runicgateway.app.ui.LocalAssetResolver
import com.runicgateway.app.ui.RunicApp
import com.runicgateway.app.ui.components.LoadingView
import com.runicgateway.app.ui.connect.ConnectScreen
import com.runicgateway.app.data.appearance.SiteAppearance
import com.runicgateway.app.ui.theme.RunicGatewayTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject

/**
 * Single-activity host (PLAN.md §2). Gates on [AppViewModel]: the first-run
 * connect screen until a shard site is configured (§3), then the main app.
 * The Material theme is resolved from the shard's published appearance (M12),
 * and asset-path resolution is provided to the whole tree.
 */
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    // Native SSO bridge — handles the runicgateway://auth/callback deep link (M9,
    // §4.2). Field-injected because the callback can arrive independent of any
    // ViewModel; a successful exchange flips the SessionManager the whole app
    // observes, and the login screen consumes SsoAuthManager.outcome.
    @Inject
    lateinit var ssoAuthManager: SsoAuthManager

    // The stream a tapped push notification wants to open (§11, M7 Part 2 item 7).
    // Set from the launching intent and from onNewIntent (the activity is singleTop),
    // consumed once by RunicApp which navigates to the stream's screen.
    private var pendingStream by mutableStateOf<String?>(null)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pendingStream = intent?.getStringExtra(PushNotifier.EXTRA_STREAM)
        handleSsoCallback(intent)
        // Dark-only app (M5): force light system-bar icons over the transparent bars so
        // they stay legible on the deep blue-black surfaces regardless of system theme.
        val barStyle = SystemBarStyle.dark(Color.TRANSPARENT)
        enableEdgeToEdge(statusBarStyle = barStyle, navigationBarStyle = barStyle)
        setContent {
            val appViewModel: AppViewModel = hiltViewModel()
            val state by appViewModel.state.collectAsStateWithLifecycle()

            // The whole theme, not just the accent (THEMING_AND_NAV.md §5.1): the
            // resolved token map is applied field by field over the shipped palette,
            // so NONE — before the site is connected, or when settings can't be
            // read — is the app exactly as it shipped.
            val appearance = (state as? AppState.Ready)?.appearance ?: SiteAppearance.NONE

            // The admin's theme and nav can change while the app is backgrounded
            // (THEMING_AND_NAV.md §5.5). Re-read them on resume, beside the session
            // re-validation RunicApp already does. Best-effort and silent.
            LifecycleResumeEffect(Unit) {
                appViewModel.refreshAppearance()
                onPauseOrDispose { }
            }

            RunicGatewayTheme(appearance = appearance) {
                CompositionLocalProvider(LocalAssetResolver provides appViewModel::resolveAsset) {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colorScheme.background,
                    ) {
                        when (val s = state) {
                            AppState.Loading -> LoadingView()
                            AppState.NeedsConnection ->
                                ConnectScreen(onConnected = appViewModel::onConnected)
                            is AppState.Ready ->
                                RunicApp(
                                    appearance = s.appearance,
                                    onChangeServer = appViewModel::changeServer,
                                    deepLinkStream = pendingStream,
                                    onDeepLinkConsumed = { pendingStream = null },
                                )
                        }
                    }
                }
            }
        }
    }

    /**
     * A notification tap or an SSO callback arriving while the activity is already
     * running (singleTop) — the common case, since the Custom Tab overlays the live
     * app during sign-in.
     */
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        intent.getStringExtra(PushNotifier.EXTRA_STREAM)?.let { pendingStream = it }
        handleSsoCallback(intent)
    }

    /**
     * Route an SSO callback VIEW intent into the bridge (M9, §4.2): either the
     * custom-scheme `runicgateway://auth/callback` (always) or the verified https
     * App Link `https://<paired-host>/mobile/callback` (opt-in hardening —
     * docs/android/APP_LINKS.md). Both feed the *same* exchange; the result surfaces
     * on `SsoAuthManager.outcome` (success signs the session in; failure shows on the
     * login screen). Non-callback intents are ignored.
     */
    private fun handleSsoCallback(intent: Intent?) {
        val data: Uri = intent?.takeIf { it.action == Intent.ACTION_VIEW }?.data ?: return
        val isCallback = ssoAuthManager.matchesCallback(data.scheme, data.host, data.path) ||
            ssoAuthManager.matchesAppLinkCallback(data.scheme, data.host, data.path)
        if (!isCallback) return
        val state = data.getQueryParameter("state")
        val code = data.getQueryParameter("code")
        val error = data.getQueryParameter("error")
        lifecycleScope.launch { ssoAuthManager.complete(state = state, code = code, error = error) }
    }
}
