import 'dart:async';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'core/api/api_client_provider.dart';
import 'core/auth/auth_notifier.dart';
import 'core/auth/auth_providers_provider.dart';
import 'core/auth/token_storage.dart';
import 'core/chat/active_session_notifier.dart';
import 'core/chat/chat_notifier.dart';
import 'core/notifications/notification_service.dart';
import 'core/theme/halo_theme.dart';
import 'firebase_options.dart';
import 'router.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Pre-warm flutter_secure_storage. The first call triggers AndroidX
  // Security MasterKey generation (RSA in Keystore) — fast on hardware-backed
  // keystores but multi-second on emulator's software-emulated TEE. Kicking
  // it off here in parallel with Firebase init hides the latency behind the
  // splash instead of paying it on the user's first interaction.
  unawaited(TokenStorage().readRefreshToken());

  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  final messaging = FirebaseMessaging.instance;
  await messaging.requestPermission();

  runApp(const ProviderScope(child: App()));
}

class App extends ConsumerStatefulWidget {
  const App({super.key});

  @override
  ConsumerState<App> createState() => _AppState();
}

class _AppState extends ConsumerState<App> with WidgetsBindingObserver {
  bool _fcmRegistered = false;
  bool _authProvidersPreloaded = false;
  // Tracks whether the OS has paused/detached this isolate. The
  // activeSessionProvider runs a 15s poll (see active_session_notifier.dart);
  // each tick fires the listener below, which would otherwise re-open the
  // chat WebSocket immediately after didChangeAppLifecycleState closed it
  // — defeating the WS→FCM fallback. We gate the reconnect on this flag so
  // the WS stays closed while the app is backgrounded, even as polling
  // continues.
  bool _appPaused = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    // Phase 4: preload server-driven auth-provider gating once on cold start.
    // Cached via @Riverpod(keepAlive: true) — subsequent reads are instant.
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_authProvidersPreloaded) return;
      _authProvidersPreloaded = true;
      ref.read(authProvidersProvider.future);
    });
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // Background → close the chat WebSocket so backend `sendMessage` falls
    // back to FCM (chat.service.js:51 — `if (!delivered) sendPushNotification`).
    // Without this, Android keeps the TCP socket alive after the Dart
    // isolate is paused, so the backend believes the customer is online and
    // never fires the push — the user sees no alert until they reopen the
    // app. See flow_customer.mermaid.md §5 (chat room) and the
    // implementation note in main.dart's activeSession listener.
    //
    // Foreground → re-establish the WS for the current active session, if
    // any. activeSessionProvider's last cached snapshot drives the target
    // session id; the chat notifier's `connectIfNotConnected` is a no-op
    // when the same session is already wired up.
    final notifier = ref.read(chatProvider.notifier);
    if (state == AppLifecycleState.paused ||
        state == AppLifecycleState.detached) {
      _appPaused = true;
      if (notifier.connectedSessionId != null) {
        notifier.disconnect();
      }
    } else if (state == AppLifecycleState.resumed) {
      _appPaused = false;
      final snapshot = ref.read(activeSessionProvider).valueOrNull;
      final sessionId = snapshot?.sessionId;
      if (sessionId != null &&
          (snapshot?.hasSession ?? false) &&
          notifier.connectedSessionId != sessionId) {
        notifier.connectIfNotConnected(sessionId);
      }
    }
  }

  void _registerFcmToken() {
    if (_fcmRegistered) return;
    _fcmRegistered = true;
    Future(() async {
      try {
        final token = await FirebaseMessaging.instance.getToken();
        if (token != null) {
          await ref.read(apiClientProvider).post('/api/shared/device-token', data: {'token': token});
        }
      } catch (_) {
        _fcmRegistered = false;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    // FCM registration on auth.
    ref.listen(authProvider, (prev, next) {
      final data = next.valueOrNull;
      if (data is AuthAuthenticatedData || data is AuthAnonymousData) {
        _registerFcmToken();
      } else {
        // Logged out (or initial) — ensure the chat WS is closed.
        ref.read(chatProvider.notifier).disconnect();
      }
    });

    // Global chat WebSocket lifecycle: connect whenever the user has an
    // active session, regardless of which screen is mounted. The chat screen
    // only joins this connection — it doesn't own it. FCM remains the
    // background-only fallback.
    //
    // Gate on `_appPaused`: activeSessionProvider runs a 15s poll that fires
    // this listener on every tick. If we reconnect while the app is
    // backgrounded, we undo the disconnect that didChangeAppLifecycleState
    // just performed and the FCM fallback never triggers for messages that
    // arrive during background.
    ref.listen(activeSessionProvider, (prev, next) {
      final snapshot = next.valueOrNull;
      final notifier = ref.read(chatProvider.notifier);
      if (snapshot == null || !snapshot.hasSession) {
        if (notifier.connectedSessionId != null) {
          notifier.disconnect();
        }
        return;
      }
      if (_appPaused) return;
      final sessionId = snapshot.sessionId;
      if (sessionId != null && notifier.connectedSessionId != sessionId) {
        notifier.connectIfNotConnected(sessionId);
      }
    });

    final router = ref.watch(routerProvider);

    NotificationService.initialize(router);

    return MaterialApp.router(
      title: 'Halo Bestie',
      theme: haloThemeData(),
      routerConfig: router,
    );
  }
}
