import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/auth/auth_notifier.dart';
import '../../../core/theme/halo_tokens.dart';
import '../../../core/theme/widgets/widgets.dart';

const int _kOtpLength = 6;
const int _kMaxAttempts = 5;
const int _kResendCooldownSeconds = 60;

/// S3b · OTP verification (6-digit) for mitra.
///
/// Visual contract mirrors `figma-bestie/project/screens/onboarding.jsx::S3Phone`
/// (OTP-step view) with the customer's 4 boxes scaled up to 6 (mitra uses
/// 6-digit OTPs from Fazpass).
class OtpScreen extends ConsumerStatefulWidget {
  final String phone;
  const OtpScreen({super.key, required this.phone});

  @override
  ConsumerState<OtpScreen> createState() => _OtpScreenState();
}

class _OtpScreenState extends ConsumerState<OtpScreen> {
  final List<TextEditingController> _controllers =
      List.generate(_kOtpLength, (_) => TextEditingController());
  final List<FocusNode> _focusNodes =
      List.generate(_kOtpLength, (_) => FocusNode());

  String? _otpRequestId;
  int _attemptsUsed = 0;
  String? _inlineError;

  Timer? _cooldownTicker;
  int _cooldown = _kResendCooldownSeconds;

  bool _isResending = false;
  bool _dialogShown = false;
  ProviderSubscription<AsyncValue<MitraAuthData>>? _authSub;

  @override
  void initState() {
    super.initState();
    final data = ref.read(mitraAuthProvider).valueOrNull;
    if (data is MitraAuthOtpSentData) {
      _otpRequestId = data.otpRequestId;
    }
    _startCooldown();

    _authSub = ref.listenManual<AsyncValue<MitraAuthData>>(mitraAuthProvider,
        (prev, next) {
      if (!mounted) return;

      // Resend completed — the notifier emits a fresh MitraAuthOtpSentData
      // with a new otp_request_id. Reset local state without bouncing back
      // to S3a (which the router would otherwise do if we let the listener
      // fire on the same OtpSentData state).
      final data = next.valueOrNull;
      if (data is MitraAuthOtpSentData && data.otpRequestId != _otpRequestId) {
        _otpRequestId = data.otpRequestId;
        setState(() {
          _attemptsUsed = 0;
          _inlineError = null;
        });
        _clearFieldsAndFocus();
        _startCooldown();
        return;
      }

      if (next is! AsyncError) return;
      final err = next.error;
      if (err is MitraAuthError) {
        _handleAuthError(err);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(err.toString())),
        );
      }
    });
  }

  @override
  void dispose() {
    _authSub?.close();
    _cooldownTicker?.cancel();
    for (final c in _controllers) {
      c.dispose();
    }
    for (final f in _focusNodes) {
      f.dispose();
    }
    super.dispose();
  }

  void _startCooldown() {
    _cooldownTicker?.cancel();
    setState(() => _cooldown = _kResendCooldownSeconds);
    _cooldownTicker = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (!mounted) {
        timer.cancel();
        return;
      }
      if (_cooldown <= 1) {
        timer.cancel();
        setState(() => _cooldown = 0);
      } else {
        setState(() => _cooldown -= 1);
      }
    });
  }

  String get _otp => _controllers.map((c) => c.text).join();

  void _clearFieldsAndFocus() {
    for (final c in _controllers) {
      c.clear();
    }
    _focusNodes[0].requestFocus();
  }

  void _onChanged(int index, String value) {
    if (value.isNotEmpty && index < _kOtpLength - 1) {
      _focusNodes[index + 1].requestFocus();
    }
    if (value.isEmpty && index > 0) {
      _focusNodes[index - 1].requestFocus();
    }
    if (_otp.length == _kOtpLength) {
      _submit();
    }
  }

  KeyEventResult _onKeyEvent(int index, KeyEvent event) {
    if (event is KeyDownEvent &&
        event.logicalKey == LogicalKeyboardKey.backspace &&
        _controllers[index].text.isEmpty &&
        index > 0) {
      _controllers[index - 1].clear();
      _focusNodes[index - 1].requestFocus();
      return KeyEventResult.handled;
    }
    return KeyEventResult.ignored;
  }

  void _submit() {
    final otp = _otp;
    if (otp.length != _kOtpLength || _otpRequestId == null) return;
    setState(() => _inlineError = null);
    ref.read(mitraAuthProvider.notifier).verifyOtp(_otpRequestId!, otp);
  }

  Future<void> _resend() async {
    if (_cooldown > 0 || _isResending) return;
    setState(() {
      _isResending = true;
      _inlineError = null;
    });
    await ref.read(mitraAuthProvider.notifier).requestOtp(widget.phone);
    if (!mounted) return;
    setState(() => _isResending = false);
  }

  Future<void> _showBlockedDialog() {
    return showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (ctx) => AlertDialog(
        title: const Text('Terlalu banyak percobaan'),
        content: const Text(
          'Kode OTP sudah salah terlalu banyak kali. Minta kode baru untuk lanjut.',
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(ctx).pop();
              if (mounted) context.pop();
            },
            child: const Text('Minta kode baru'),
          ),
        ],
      ),
    );
  }

  Future<void> _showResetDialog(MitraAuthError err, {required String title}) {
    return showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (ctx) => AlertDialog(
        title: Text(title),
        content: Text(err.message),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(ctx).pop();
              if (mounted) context.pop();
            },
            child: const Text('Minta kode baru'),
          ),
        ],
      ),
    );
  }

  Future<void> _showWrongFlowDialog(MitraAuthError err) {
    return showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (ctx) => AlertDialog(
        title: const Text('Bukan akun mitra'),
        content: Text(
          '${err.message}\n\nPastikan kamu pakai aplikasi yang benar.',
        ),
        actions: [
          TextButton(
            onPressed: () {
              Navigator.of(ctx).pop();
              if (mounted) context.pop();
            },
            child: const Text('Kembali'),
          ),
        ],
      ),
    );
  }

  Future<void> _handleAuthError(MitraAuthError err) async {
    if (_dialogShown) return;
    switch (err.code) {
      case 'CODE_INVALID':
        setState(() => _inlineError = err.message);
        break;
      case 'CODE_MISMATCH':
        setState(() {
          _attemptsUsed = (_attemptsUsed + 1).clamp(0, _kMaxAttempts);
          final remaining = _kMaxAttempts - _attemptsUsed;
          _inlineError = remaining > 0
              ? 'Kode salah. Tersisa $remaining percobaan.'
              : 'Kode salah.';
        });
        _clearFieldsAndFocus();
        break;
      case 'OTP_ATTEMPTS_EXCEEDED':
        _dialogShown = true;
        await _showBlockedDialog();
        _dialogShown = false;
        break;
      case 'OTP_EXPIRED':
        _dialogShown = true;
        await _showResetDialog(err, title: 'Kode kedaluwarsa');
        _dialogShown = false;
        break;
      case 'OTP_USED':
        _dialogShown = true;
        await _showResetDialog(err, title: 'Kode sudah dipakai');
        _dialogShown = false;
        break;
      case 'WRONG_FLOW':
        _dialogShown = true;
        await _showWrongFlowDialog(err);
        _dialogShown = false;
        break;
      case 'ACCOUNT_INACTIVE':
        if (mounted) context.go('/auth/inactive');
        break;
      case 'OTP_COOLDOWN':
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(err.message)),
        );
        break;
      default:
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(err.message)),
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    final authState = ref.watch(mitraAuthProvider);
    final isLoading = authState is AsyncLoading;

    final resendLabel = _cooldown > 0
        ? 'kirim ulang dalam ${_cooldown}s'
        : 'kirim ulang kode';
    final resendEnabled = _cooldown == 0 && !_isResending && !isLoading;

    return Scaffold(
      backgroundColor: HaloTokens.bg,
      appBar: AppBar(
        backgroundColor: HaloTokens.bg,
        leading: IconButton(
          icon: const Icon(Icons.arrow_back, color: HaloTokens.ink),
          onPressed: () => context.pop(),
        ),
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.fromLTRB(28, 0, 28, 28),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Expanded(
                child: LayoutBuilder(
                  builder: (context, constraints) => SingleChildScrollView(
                    child: ConstrainedBox(
                      constraints: BoxConstraints(minHeight: constraints.maxHeight),
                      child: IntrinsicHeight(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          children: [
                            const Text(
                              'masukin 6 digit kode',
                              style: TextStyle(
                                fontFamily: HaloTokens.fontDisplay,
                                fontSize: 28,
                                fontWeight: FontWeight.w700,
                                color: HaloTokens.brandDark,
                                height: 1.15,
                                letterSpacing: -0.56,
                              ),
                            ),
                            const SizedBox(height: 10),
                            RichText(
                              text: TextSpan(
                                style: const TextStyle(
                                  fontFamily: HaloTokens.fontBody,
                                  fontSize: 14.5,
                                  color: HaloTokens.inkSoft,
                                  height: 1.5,
                                ),
                                children: [
                                  const TextSpan(text: 'kami baru kirim ke WA '),
                                  TextSpan(
                                    text: widget.phone,
                                    style: const TextStyle(
                                      fontWeight: FontWeight.w700,
                                      color: HaloTokens.ink,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                            const SizedBox(height: 28),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: List.generate(_kOtpLength, (i) {
                                return _OtpBox(
                                  controller: _controllers[i],
                                  focusNode: _focusNodes[i],
                                  autofocus: i == 0,
                                  onChanged: (v) => _onChanged(i, v),
                                  onKeyEvent: (e) => _onKeyEvent(i, e),
                                  hasError: _inlineError != null,
                                );
                              }),
                            ),
                            if (_inlineError != null) ...[
                              const SizedBox(height: 12),
                              Text(
                                _inlineError!,
                                textAlign: TextAlign.center,
                                style: const TextStyle(
                                  fontFamily: HaloTokens.fontBody,
                                  color: HaloTokens.danger,
                                  fontSize: 13,
                                ),
                              ),
                            ],
                            const SizedBox(height: 20),
                            Row(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                const Text(
                                  'gak nyampe? ',
                                  style: TextStyle(
                                    fontFamily: HaloTokens.fontBody,
                                    fontSize: 13,
                                    color: HaloTokens.inkSoft,
                                  ),
                                ),
                                GestureDetector(
                                  onTap: resendEnabled ? _resend : null,
                                  child: Text(
                                    resendLabel,
                                    style: TextStyle(
                                      fontFamily: HaloTokens.fontBody,
                                      fontSize: 13,
                                      fontWeight: FontWeight.w600,
                                      color: resendEnabled
                                          ? HaloTokens.brandDark
                                          : HaloTokens.inkMuted,
                                    ),
                                  ),
                                ),
                              ],
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
                ),
              ),
              HaloButton(
                label: isLoading ? 'memproses...' : 'verifikasi',
                fullWidth: true,
                onPressed: (isLoading || _otp.length != _kOtpLength) ? null : _submit,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _OtpBox extends StatelessWidget {
  final TextEditingController controller;
  final FocusNode focusNode;
  final bool autofocus;
  final ValueChanged<String> onChanged;
  // Use `Focus(canRequestFocus: false)` not `KeyboardListener` — the latter
  // spawns an extra FocusNode that swallows IME input on Android, which
  // breaks both maestro `inputText` and SMS auto-paste. Matches the customer
  // app pattern in client_app/.../otp_screen.dart::_buildBox.
  final KeyEventResult Function(KeyEvent) onKeyEvent;
  final bool hasError;

  const _OtpBox({
    required this.controller,
    required this.focusNode,
    required this.autofocus,
    required this.onChanged,
    required this.onKeyEvent,
    required this.hasError,
  });

  @override
  Widget build(BuildContext context) {
    final filled = controller.text.isNotEmpty;
    final borderColor = hasError
        ? HaloTokens.danger
        : filled
            ? HaloTokens.brand
            : HaloTokens.border;
    return SizedBox(
      width: 48,
      height: 60,
      child: Focus(
        canRequestFocus: false,
        onKeyEvent: (_, event) => onKeyEvent(event),
        child: TextField(
          controller: controller,
          focusNode: focusNode,
          autofocus: autofocus,
          textAlign: TextAlign.center,
          keyboardType: TextInputType.number,
          maxLength: 1,
          style: const TextStyle(
            fontFamily: HaloTokens.fontBody,
            fontSize: 24,
            fontWeight: FontWeight.w700,
            color: HaloTokens.ink,
          ),
          decoration: InputDecoration(
            counterText: '',
            filled: true,
            fillColor: HaloTokens.surface,
            contentPadding: const EdgeInsets.symmetric(vertical: 14),
            border: OutlineInputBorder(
              borderRadius: HaloRadius.md,
              borderSide: BorderSide(color: borderColor, width: 1.5),
            ),
            enabledBorder: OutlineInputBorder(
              borderRadius: HaloRadius.md,
              borderSide: BorderSide(color: borderColor, width: 1.5),
            ),
            focusedBorder: OutlineInputBorder(
              borderRadius: HaloRadius.md,
              borderSide: BorderSide(
                color: hasError ? HaloTokens.danger : HaloTokens.brand,
                width: 2,
              ),
            ),
          ),
          inputFormatters: [FilteringTextInputFormatter.digitsOnly],
          onChanged: onChanged,
        ),
      ),
    );
  }
}

