import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../chat_request_notifier.dart';
import '../../constants.dart';
import '../../../router.dart';
import '../../theme/halo_tokens.dart';
import 'sensitivity_badge.dart';
import 'sensitivity_theme.dart';

/// Centered modal overlay for incoming chat requests.
///
/// Visual restyle of the Figma `BestieIncomingPopup` (v5.jsx:129). Two
/// variants are rendered from the same widget:
///   - new (PairingRequestType.general) — pink-bordered card, 📨 emoji,
///     'Curhat Baru!' headline, 'Terima' button.
///   - extend (PairingRequestType.returning) — amber-bordered card, ⚡ emoji,
///     'Perpanjang Curhat' headline, 'Terima Perpanjangan' button, '+N mnt'
///     duration badge.
///
/// The accept/decline behavior, countdown timer, and stale handling are
/// unchanged from the prior bottom-sheet implementation; only the visual
/// layout differs.
class ChatRequestOverlay extends ConsumerStatefulWidget {
  final Widget child;
  const ChatRequestOverlay({super.key, required this.child});

  @override
  ConsumerState<ChatRequestOverlay> createState() => _ChatRequestOverlayState();
}

class _ChatRequestOverlayState extends ConsumerState<ChatRequestOverlay>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animController;
  late final Animation<double> _scaleAnimation;
  bool _visible = false;

  // Returning-chat countdown. Server is the source of truth on auto-reject;
  // this is purely visual. When it hits 0 we dismiss the overlay and let the
  // server's chat_request_closed event (or stale state) take over.
  Timer? _countdownTimer;
  int? _secondsRemaining;
  String? _countdownSessionId;

  // Tracks the last sessionId we surfaced a 'taken by other Bestie' snackbar
  // for, so re-entering the same stale state doesn't queue duplicates.
  String? _lastStaleSnackbarSessionId;

  // Snapshot of the most recently displayed incoming request. Kept so the
  // card can stay visible during ChatRequestAcceptingData (the notifier
  // drops the incoming payload on that transition) and render the spinner
  // inside the accept button instead of swapping to a placeholder card.
  ChatRequestIncomingData? _lastIncoming;

  @override
  void initState() {
    super.initState();
    _animController = AnimationController(
      vsync: this,
      duration: HaloMotion.normal,
    );
    _scaleAnimation = CurvedAnimation(
      parent: _animController,
      curve: HaloMotion.ease,
    );
  }

  @override
  void dispose() {
    _stopCountdown();
    _animController.dispose();
    super.dispose();
  }

  void _show() {
    if (!_visible) {
      setState(() => _visible = true);
      _animController.forward();
    }
  }

  void _hide() {
    _stopCountdown();
    _animController.reverse().then((_) {
      if (mounted) setState(() => _visible = false);
    });
  }

  void _startCountdown(String sessionId, int seconds) {
    _stopCountdown();
    _countdownSessionId = sessionId;
    _secondsRemaining = seconds;
    _countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) {
      if (!mounted) return;
      final remaining = (_secondsRemaining ?? 0) - 1;
      if (remaining <= 0) {
        // Auto-dismiss UI only — server fires the actual auto-reject and will
        // follow up with chat_request_closed. Do NOT call decline from here.
        setState(() => _secondsRemaining = 0);
        _stopCountdown();
        _hide();
      } else {
        setState(() => _secondsRemaining = remaining);
      }
    });
  }

  void _stopCountdown() {
    _countdownTimer?.cancel();
    _countdownTimer = null;
    _countdownSessionId = null;
    _secondsRemaining = null;
  }

  void _maybeStartCountdownFor(ChatRequestIncomingData data) {
    // Both variants now show a live countdown to be consistent with Figma
    // ('30 detik' for new, '10 detik' for extend). Fall back to a sensible
    // default if the server didn't send a timeout.
    final timeout = data.confirmationTimeoutSeconds ??
        (data.requestType == PairingRequestType.returning ? 10 : 30);
    if (_countdownSessionId != data.sessionId) {
      _startCountdown(data.sessionId, timeout);
    }
  }

  @override
  Widget build(BuildContext context) {
    ref.listen(chatRequestProvider, (prev, next) {
      if (next is ChatRequestIncomingData) {
        _lastIncoming = next;
        _show();
        _maybeStartCountdownFor(next);
      } else if (next is ChatRequestStaleData) {
        _stopCountdown();
        // Race-condition path: another Bestie grabbed it. Show a transient
        // snackbar instead of the modal card, and auto-advance the queue so
        // the next pending request (if any) takes over.
        if (next.reason == StaleReason.acceptedByOther &&
            _lastStaleSnackbarSessionId != next.sessionId) {
          _lastStaleSnackbarSessionId = next.sessionId;
          _hide();
          WidgetsBinding.instance.addPostFrameCallback((_) {
            if (!mounted) return;
            final messenger = ScaffoldMessenger.maybeOf(context);
            messenger?.hideCurrentSnackBar();
            messenger?.showSnackBar(
              const SnackBar(
                content: Text('Pesan sudah diambil bestie lain 💛'),
                duration: Duration(seconds: 3),
                behavior: SnackBarBehavior.floating,
              ),
            );
          });
          // Auto-ack so the notifier moves on to the next queued invite.
          ref.read(chatRequestProvider.notifier).acknowledgeStale();
        } else {
          _show();
        }
      } else if (next is ChatRequestAcceptedData) {
        _hide();
        // Navigate to chat session
        final session = next.session;
        final sessionId =
            session['session_id'] as String? ?? session['id'] as String;
        final router = ref.read(routerProvider);
        router.push('/chat/session/$sessionId', extra: {
          'customerName':
              session['customer_display_name'] as String? ?? 'Customer',
        });
      } else if (next is ChatRequestAcceptingData) {
        // Keep the modal visible while the accept call is in flight; the
        // button content swaps to a spinner. _lastIncoming retains the
        // card content.
      } else {
        _lastIncoming = null;
        _hide();
      }
    });

    return Directionality(
      textDirection: TextDirection.ltr,
      child: Stack(
        children: [
          widget.child,
          if (_visible)
            Positioned.fill(
              child: FadeTransition(
                opacity: _animController,
                child: Stack(
                  children: [
                    // Dim backdrop — taps are absorbed but DO NOT dismiss.
                    Positioned.fill(
                      child: GestureDetector(
                        behavior: HitTestBehavior.opaque,
                        onTap: () {},
                        child: Container(
                          color: Colors.black.withValues(alpha: 0.5),
                        ),
                      ),
                    ),
                    Center(
                      child: ScaleTransition(
                        scale: Tween<double>(begin: 0.92, end: 1.0)
                            .animate(_scaleAnimation),
                        child: SafeArea(
                          child: Padding(
                            padding: const EdgeInsets.symmetric(
                              horizontal: HaloSpacing.s20,
                            ),
                            child: ConstrainedBox(
                              constraints: const BoxConstraints(maxWidth: 360),
                              child: _buildContent(),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildContent() {
    final requestState = ref.watch(chatRequestProvider);

    if (requestState is ChatRequestIncomingData) {
      return _IncomingCard(
        data: requestState,
        secondsRemaining:
            _countdownSessionId == requestState.sessionId ? _secondsRemaining : null,
        accepting: false,
        onAccept: () => ref
            .read(chatRequestProvider.notifier)
            .accept(requestState.sessionId),
        onDecline: () => ref
            .read(chatRequestProvider.notifier)
            .decline(requestState.sessionId),
      );
    }
    if (requestState is ChatRequestAcceptingData) {
      // Keep the same card visible with both buttons disabled and the
      // accept button replaced by a spinner. Falls back to a minimal
      // placeholder card if for any reason we don't have the prior data
      // (e.g. cold start via setIncomingFromNotification).
      final prev = _lastIncoming;
      if (prev == null) return const _AcceptingCard();
      return _IncomingCard(
        data: prev,
        secondsRemaining:
            _countdownSessionId == prev.sessionId ? _secondsRemaining : null,
        accepting: true,
        onAccept: () {},
        onDecline: () {},
      );
    }
    if (requestState is ChatRequestStaleData) {
      return _StaleCard(
        data: requestState,
        onAck: () =>
            ref.read(chatRequestProvider.notifier).acknowledgeStale(),
      );
    }
    return const SizedBox.shrink();
  }
}

/// Pink/amber bordered modal card for an active incoming request.
class _IncomingCard extends StatelessWidget {
  const _IncomingCard({
    required this.data,
    required this.secondsRemaining,
    required this.accepting,
    required this.onAccept,
    required this.onDecline,
  });

  final ChatRequestIncomingData data;
  final int? secondsRemaining;
  final bool accepting;
  final VoidCallback onAccept;
  final VoidCallback onDecline;

  bool get _isExtend => data.requestType == PairingRequestType.returning;

  Color get _accent =>
      _isExtend ? HaloTokens.accentAmber : HaloTokens.brand;

  String get _emoji => _isExtend ? '⚡' : '📨';

  String get _headline => _isExtend ? 'Perpanjang Curhat' : 'Curhat Baru!';

  String get _acceptLabel {
    if (_isExtend) {
      final mins = data.durationMinutes;
      return mins != null ? 'Terima · +$mins mnt' : 'Terima Perpanjangan';
    }
    return 'Terima';
  }

  @override
  Widget build(BuildContext context) {
    final isSensitive = data.topicSensitivity == TopicSensitivity.sensitive;
    final sensitivityTheme = SensitivityTheme.of(data.topicSensitivity);

    final defaultCountdown = _isExtend ? 10 : 30;
    final remaining = secondsRemaining ?? defaultCountdown;
    final urgent = remaining <= 10;
    final countdownColor =
        urgent ? HaloTokens.danger : HaloTokens.inkSoft;

    return Container(
      decoration: BoxDecoration(
        color: HaloTokens.surface,
        borderRadius: HaloRadius.lg,
        border: Border.all(color: _accent, width: 2),
        boxShadow: HaloShadows.card,
      ),
      padding: const EdgeInsets.fromLTRB(
        HaloSpacing.s24,
        HaloSpacing.s24,
        HaloSpacing.s24,
        HaloSpacing.s20,
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Header: emoji + headline
          Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Text(
                _emoji,
                style: const TextStyle(fontSize: 32),
              ),
              const SizedBox(width: HaloSpacing.s12),
              Expanded(
                child: Text(
                  _headline,
                  style: TextStyle(
                    fontFamily: HaloTokens.fontDisplay,
                    fontSize: 22,
                    fontWeight: FontWeight.w700,
                    color: _isExtend ? HaloTokens.accentAmber : HaloTokens.brandDark,
                    height: 1.15,
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: HaloSpacing.s16),
          // Countdown line — color shifts to danger when <=10s.
          Text(
            '$remaining detik untuk respon',
            style: TextStyle(
              fontFamily: HaloTokens.fontBody,
              fontSize: 13,
              fontWeight: urgent ? FontWeight.w600 : FontWeight.w500,
              color: countdownColor,
            ),
          ),
          // Extend variant only: '+N mnt' duration badge.
          if (_isExtend && data.durationMinutes != null) ...[
            const SizedBox(height: HaloSpacing.s8),
            Align(
              alignment: Alignment.centerLeft,
              child: Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: HaloSpacing.s12,
                  vertical: 4,
                ),
                decoration: const BoxDecoration(
                  color: HaloTokens.accentAmberSoft,
                  borderRadius: HaloRadius.pill,
                ),
                child: Text(
                  '+${data.durationMinutes} mnt',
                  style: const TextStyle(
                    fontFamily: HaloTokens.fontBody,
                    fontSize: 12,
                    fontWeight: FontWeight.w700,
                    color: HaloTokens.accentAmber,
                  ),
                ),
              ),
            ),
          ],
          const SizedBox(height: HaloSpacing.s12),
          // Optional details line (mode / customer name not in state yet —
          // per the Stage 3 finding, ChatRequestIncomingData doesn't carry
          // customer display name. Fall back to generic copy.)
          Text(
            _isExtend
                ? 'Klien lama mau lanjutin curhat sama kamu.'
                : 'Pesan masuk dari user — siap dengerin?',
            style: const TextStyle(
              fontFamily: HaloTokens.fontBody,
              fontSize: 13,
              color: HaloTokens.inkSoft,
              height: 1.4,
            ),
          ),
          if (isSensitive) ...[
            const SizedBox(height: HaloSpacing.s8),
            Align(
              alignment: Alignment.centerLeft,
              child: SensitivityBadge(
                sensitivity: data.topicSensitivity,
                fontSize: 11,
              ),
            ),
          ],
          // Show free-trial / duration meta as a subtle row (new variant only).
          if (!_isExtend &&
              (data.isFreeTrial == true || data.durationMinutes != null)) ...[
            const SizedBox(height: HaloSpacing.s8),
            Text(
              data.isFreeTrial == true
                  ? 'Durasi: Free Trial'
                  : 'Durasi: ${data.durationMinutes} menit',
              style: const TextStyle(
                fontFamily: HaloTokens.fontBody,
                fontSize: 12,
                color: HaloTokens.inkMuted,
              ),
            ),
          ],
          const SizedBox(height: HaloSpacing.s20),
          // Button row
          Row(
            children: [
              Expanded(
                child: OutlinedButton(
                  onPressed: accepting ? null : onDecline,
                  style: OutlinedButton.styleFrom(
                    foregroundColor: HaloTokens.inkSoft,
                    backgroundColor: HaloTokens.brandSofter,
                    disabledForegroundColor: HaloTokens.inkMuted,
                    side: BorderSide.none,
                    padding: const EdgeInsets.symmetric(vertical: 14),
                    shape: const RoundedRectangleBorder(
                      borderRadius: HaloRadius.md,
                    ),
                    textStyle: const TextStyle(
                      fontFamily: HaloTokens.fontBody,
                      fontSize: 13,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                  child: const Text('Tolak'),
                ),
              ),
              const SizedBox(width: HaloSpacing.s8),
              Expanded(
                flex: 2,
                child: _AcceptButton(
                  label: _acceptLabel,
                  accent: _accent,
                  accenting: accepting,
                  onPressed: accepting ? null : onAccept,
                  sensitivityTint:
                      isSensitive ? sensitivityTheme.badgeBg : null,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

/// Filled accept button. Renders pink (HaloTokens.brand) for the new variant
/// and amber (HaloTokens.accentAmber) for the extend variant — same pattern
/// as `_PrimaryAmberButton` in `undangan_screen.dart`. Kept inline rather
/// than extending HaloButton, since adding a `backgroundColor` override to
/// HaloButton would touch every existing call site.
class _AcceptButton extends StatelessWidget {
  const _AcceptButton({
    required this.label,
    required this.accent,
    required this.accenting,
    required this.onPressed,
    required this.sensitivityTint,
  });

  final String label;
  final Color accent;
  final bool accenting;
  final VoidCallback? onPressed;
  final Color? sensitivityTint;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: accent,
        foregroundColor: Colors.white,
        disabledBackgroundColor: accent.withValues(alpha: 0.5),
        disabledForegroundColor: Colors.white,
        elevation: 0,
        padding: const EdgeInsets.symmetric(vertical: 14),
        shape: const RoundedRectangleBorder(borderRadius: HaloRadius.md),
        textStyle: const TextStyle(
          fontFamily: HaloTokens.fontBody,
          fontSize: 13,
          fontWeight: FontWeight.w700,
        ),
      ),
      child: accenting
          ? const SizedBox(
              height: 16,
              width: 16,
              child: CircularProgressIndicator(
                strokeWidth: 2,
                valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
              ),
            )
          : Text(label),
    );
  }
}

/// In-flight accept state — minimal card with spinner. The notifier drops
/// the previous incoming data on transition to `ChatRequestAcceptingData`,
/// so we render a small placeholder rather than mirror the full card.
class _AcceptingCard extends StatelessWidget {
  const _AcceptingCard();

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: HaloTokens.surface,
        borderRadius: HaloRadius.lg,
        border: Border.all(color: HaloTokens.brand, width: 2),
        boxShadow: HaloShadows.card,
      ),
      padding: const EdgeInsets.all(HaloSpacing.s24),
      child: const Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          SizedBox(
            width: 18,
            height: 18,
            child: CircularProgressIndicator(
              strokeWidth: 2,
              valueColor: AlwaysStoppedAnimation<Color>(HaloTokens.brand),
            ),
          ),
          SizedBox(width: HaloSpacing.s12),
          Flexible(
            child: Text(
              'Menerima permintaan...',
              style: TextStyle(
                fontFamily: HaloTokens.fontBody,
                fontSize: 14,
                fontWeight: FontWeight.w600,
                color: HaloTokens.ink,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Restyled stale card for non-race stale reasons (cancelled / expired).
/// The `acceptedByOther` race is handled via snackbar in the listener, so it
/// never reaches this widget.
class _StaleCard extends StatelessWidget {
  const _StaleCard({required this.data, required this.onAck});

  final ChatRequestStaleData data;
  final VoidCallback onAck;

  @override
  Widget build(BuildContext context) {
    final message = switch (data.reason) {
      StaleReason.cancelledByCustomer => 'Permintaan dibatalkan oleh klien',
      StaleReason.acceptedByOther => 'Permintaan diterima bestie lain',
      StaleReason.expired => 'Permintaan kedaluwarsa',
    };

    return Container(
      decoration: BoxDecoration(
        color: HaloTokens.surface,
        borderRadius: HaloRadius.lg,
        border: Border.all(color: HaloTokens.border, width: 1),
        boxShadow: HaloShadows.card,
      ),
      padding: const EdgeInsets.fromLTRB(
        HaloSpacing.s24,
        HaloSpacing.s24,
        HaloSpacing.s24,
        HaloSpacing.s20,
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          const Text(
            '⏱',
            style: TextStyle(fontSize: 32),
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: HaloSpacing.s12),
          Text(
            message,
            style: const TextStyle(
              fontFamily: HaloTokens.fontDisplay,
              fontSize: 17,
              fontWeight: FontWeight.w700,
              color: HaloTokens.ink,
            ),
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: HaloSpacing.s20),
          ElevatedButton(
            onPressed: onAck,
            style: ElevatedButton.styleFrom(
              backgroundColor: HaloTokens.brand,
              foregroundColor: Colors.white,
              elevation: 0,
              padding: const EdgeInsets.symmetric(vertical: 14),
              shape: const RoundedRectangleBorder(
                borderRadius: HaloRadius.md,
              ),
              textStyle: const TextStyle(
                fontFamily: HaloTokens.fontBody,
                fontSize: 13,
                fontWeight: FontWeight.w700,
              ),
            ),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }
}
