import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../core/api/api_client_provider.dart';
import '../../../core/constants.dart';
import '../../../core/theme/halo_tokens.dart';
import '../state/payment_draft_provider.dart';

/// "Waiting payment" — placeholder QR + 20-minute countdown header. Polls the
/// backend every 3 seconds for status changes. On `confirmed` the payment is
/// considered paid; on `expired` we route to the expired screen.
///
/// Polling is paused while the app is backgrounded and resumed on foreground
/// (per the `WidgetsBindingObserver` pattern used elsewhere in the app).
class WaitingPaymentScreen extends ConsumerStatefulWidget {
  final String paymentId;
  const WaitingPaymentScreen({super.key, required this.paymentId});

  @override
  ConsumerState<WaitingPaymentScreen> createState() => _WaitingPaymentScreenState();
}

class _WaitingPaymentScreenState extends ConsumerState<WaitingPaymentScreen>
    with WidgetsBindingObserver {
  static const Duration _pollInterval = Duration(seconds: 3);
  static const Duration _tickInterval = Duration(seconds: 1);

  Timer? _ticker;
  Timer? _poller;
  DateTime? _expiresAt;
  int _amount = 0;
  String? _qrPayload;
  bool _initialLoading = true;
  bool _terminal = false;
  String? _error;
  bool _invoiceUrlLaunched = false;   // Phase 5: only auto-launch the Custom Tab once

  Duration get _remaining {
    final exp = _expiresAt;
    if (exp == null) return Duration.zero;
    final left = exp.difference(DateTime.now());
    return left.isNegative ? Duration.zero : left;
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    Future.microtask(_loadInitial);
  }

  @override
  void dispose() {
    _ticker?.cancel();
    _poller?.cancel();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _resumePolling();
    } else if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) {
      _poller?.cancel();
      _poller = null;
    }
  }

  Future<void> _loadInitial() async {
    final session = await _fetchSession();
    if (!mounted || session == null) return;
    final expiresAtRaw = session['expires_at'] as String?;
    final expiresAt = expiresAtRaw != null ? DateTime.tryParse(expiresAtRaw) : null;
    setState(() {
      _amount = (session['amount'] as int?) ?? 0;
      _expiresAt = expiresAt;
      _qrPayload = (session['qr_string'] as String?) ?? widget.paymentId;
      _initialLoading = false;
    });
    // Phase 5: when Xendit is on, the backend returns an `xendit_invoice_url`
    // (Xendit's hosted checkout). Open it in a Custom Tab (Android) /
    // SFSafariViewController (iOS) so the customer stays inside the app's
    // browser context. Fire-and-forget — polling continues regardless.
    // When Xendit is off (dev/Maestro), invoice_url is null and the QR fallback below is used.
    await _maybeLaunchInvoiceUrl(session);
    _maybeHandleStatus(session);
    _startTicker();
    _resumePolling();
  }

  Future<void> _maybeLaunchInvoiceUrl(Map<String, dynamic> session) async {
    if (_invoiceUrlLaunched) return;
    final url = (session['xendit_invoice_url'] as String?) ?? (session['invoice_url'] as String?);
    if (url == null || url.isEmpty) return;
    _invoiceUrlLaunched = true;
    try {
      await launchUrl(
        Uri.parse(url),
        mode: LaunchMode.inAppBrowserView,    // Custom Tab on Android, SFVC on iOS
      );
    } catch (e) {
      // Silent — polling will eventually resolve to expired if the customer can't pay.
      // Don't surface an error toast; the user might have a non-Custom-Tab-capable env
      // and url_launcher falls back to the system browser automatically.
    }
  }

  void _startTicker() {
    _ticker?.cancel();
    _ticker = Timer.periodic(_tickInterval, (_) {
      if (!mounted) return;
      // Trigger a rebuild to refresh the countdown label. Status routing
      // happens off the polled response, not the local clock — backend is
      // the source of truth for `expired`.
      setState(() {});
    });
  }

  void _resumePolling() {
    if (_terminal) return;
    _poller?.cancel();
    _poller = Timer.periodic(_pollInterval, (_) => _pollOnce());
  }

  Future<void> _pollOnce() async {
    final session = await _fetchSession();
    if (!mounted || session == null) return;
    _maybeHandleStatus(session);
  }

  Future<Map<String, dynamic>?> _fetchSession() async {
    try {
      final api = ref.read(apiClientProvider);
      final response = await api.get('/api/client/payment-requests/${widget.paymentId}');
      return response['data'] as Map<String, dynamic>?;
    } catch (e) {
      if (!mounted) return null;
      setState(() => _error = 'Gagal memeriksa status pembayaran.');
      return null;
    }
  }

  void _maybeHandleStatus(Map<String, dynamic> session) {
    final status = session['status'] as String?;
    if (status == PaymentRequestStatus.confirmed ||
        status == PaymentRequestStatus.consumed) {
      _markTerminal();
      _navigateTerminal('/onboarding/notif-gate');
    } else if (status == PaymentRequestStatus.expired ||
        status == PaymentRequestStatus.abandoned) {
      _markTerminal();
      _navigateTerminal('/payment/expired/${widget.paymentId}');
    }
  }

  /// Routes off the waiting screen once the payment session reached a
  /// terminal status. Belt-and-braces:
  ///   - `Future.microtask` runs after the current event loop turn (after any
  ///     pending setState), so we don't fight an in-flight build.
  ///   - `addPostFrameCallback` is a fallback in case the microtask is
  ///     pre-empted (observed once on release builds where the screen stayed
  ///     visually stuck on "menunggu pembayaran" despite polling having
  ///     stopped — see 2026-05-14 thread).
  void _navigateTerminal(String route) {
    Future.microtask(() {
      if (!mounted) return;
      context.go(route);
    });
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      // No-op if the microtask already navigated — `go` to the same location
      // is idempotent in GoRouter.
      context.go(route);
    });
  }

  void _markTerminal() {
    _terminal = true;
    _ticker?.cancel();
    _poller?.cancel();
  }

  String _countdownLabel() {
    final r = _remaining;
    final mm = r.inMinutes;
    final ss = r.inSeconds % 60;
    return '${mm.toString().padLeft(2, '0')}:${ss.toString().padLeft(2, '0')}';
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: true,
      child: Scaffold(
        backgroundColor: HaloTokens.bg,
        appBar: AppBar(
          backgroundColor: HaloTokens.bg,
          elevation: 0,
          leading: IconButton(
            icon: const Icon(Icons.chevron_left, color: HaloTokens.brandDark),
            onPressed: () {
              if (context.canPop()) {
                context.pop();
              } else {
                context.go('/home');
              }
            },
          ),
          centerTitle: true,
          title: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text(
                'kedaluwarsa dalam',
                style: TextStyle(fontSize: 11, color: HaloTokens.inkMuted),
              ),
              Text(
                _initialLoading ? '--:--' : _countdownLabel(),
                style: const TextStyle(
                  fontFamily: HaloTokens.fontMono,
                  fontSize: 16,
                  fontWeight: FontWeight.w700,
                  color: HaloTokens.brandDark,
                  letterSpacing: -0.5,
                ),
              ),
            ],
          ),
        ),
        body: _initialLoading
            ? const Center(child: CircularProgressIndicator())
            : _buildContent(),
      ),
    );
  }

  Widget _buildContent() {
    final draft = ref.watch(paymentDraftNotifierProvider);
    final amount = _amount > 0 ? _amount : (draft.priceIDR ?? 0);
    return Column(
      children: [
        Expanded(
          child: SingleChildScrollView(
            padding: const EdgeInsets.fromLTRB(
              HaloSpacing.s24,
              HaloSpacing.s8,
              HaloSpacing.s24,
              HaloSpacing.s16,
            ),
            child: Column(
              children: [
                Container(
                  padding: const EdgeInsets.all(HaloSpacing.s24),
                  decoration: BoxDecoration(
                    color: HaloTokens.surface,
                    borderRadius: HaloRadius.xl,
                    border: Border.all(color: HaloTokens.border),
                  ),
                  child: Column(
                    children: [
                      const Text(
                        'scan QRIS untuk bayar',
                        style: TextStyle(fontSize: 12, color: HaloTokens.inkSoft),
                      ),
                      const SizedBox(height: HaloSpacing.s12),
                      Container(
                        padding: const EdgeInsets.all(HaloSpacing.s12),
                        decoration: BoxDecoration(
                          color: HaloTokens.surface,
                          borderRadius: HaloRadius.lg,
                          border: Border.all(color: HaloTokens.border),
                        ),
                        child: QrImageView(
                          data: _qrPayload ?? widget.paymentId,
                          size: 200,
                          version: QrVersions.auto,
                          backgroundColor: HaloTokens.surface,
                          eyeStyle: const QrEyeStyle(
                            eyeShape: QrEyeShape.square,
                            color: HaloTokens.ink,
                          ),
                          dataModuleStyle: const QrDataModuleStyle(
                            dataModuleShape: QrDataModuleShape.square,
                            color: HaloTokens.ink,
                          ),
                        ),
                      ),
                      const SizedBox(height: HaloSpacing.s12),
                      const Text(
                        'jumlah',
                        style: TextStyle(fontSize: 11.5, color: HaloTokens.inkSoft),
                      ),
                      Text(
                        formatRupiah(amount),
                        style: const TextStyle(
                          fontFamily: HaloTokens.fontDisplay,
                          fontSize: 24,
                          fontWeight: FontWeight.w700,
                          color: HaloTokens.brandDark,
                          letterSpacing: -0.5,
                        ),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: HaloSpacing.s12),
                Container(
                  padding: const EdgeInsets.all(HaloSpacing.s12),
                  decoration: BoxDecoration(
                    color: HaloTokens.brandSofter,
                    borderRadius: HaloRadius.md,
                    border: Border.all(color: HaloTokens.brandSoft),
                  ),
                  child: Row(
                    children: [
                      Container(
                        width: 8,
                        height: 8,
                        decoration: const BoxDecoration(
                          shape: BoxShape.circle,
                          color: HaloTokens.brand,
                        ),
                      ),
                      const SizedBox(width: HaloSpacing.s8),
                      const Expanded(
                        child: Text(
                          'menunggu pembayaran kamu...',
                          style: TextStyle(
                            fontSize: 12,
                            color: HaloTokens.brandDark,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
                if (_error != null) ...[
                  const SizedBox(height: HaloSpacing.s8),
                  Text(
                    _error!,
                    style: const TextStyle(fontSize: 12, color: HaloTokens.danger),
                  ),
                ],
              ],
            ),
          ),
        ),
      ],
    );
  }
}
