import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/api/api_client_provider.dart';
import '../../../core/auth/auth_notifier.dart';
import '../../../core/constants.dart';
import '../../../core/theme/halo_tokens.dart';
import '../widgets/otp_blocked_popup.dart';

const int _kOtpLength = 6;
const int _kFallbackResendCooldownSeconds = 60;

// Codes that mean "the user cannot make progress without waiting" — these
// trip the OTP-blocked popup. Mirrors backend `otp.service.js`.
const _kOtpBlockedCodes = {
  'OTP_RATE_LIMIT_PHONE',
  'OTP_RATE_LIMIT_IP',
  'OTP_COOLDOWN',
  'OTP_ATTEMPTS_EXCEEDED',
};

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;
  bool _autoSubmitted = false;
  String? _errorMessage;
  bool _blockedPopupShown = false;

  int _resendSeconds = _kFallbackResendCooldownSeconds;
  int _resendCooldown = _kFallbackResendCooldownSeconds;
  Timer? _resendTimer;
  ProviderSubscription<AsyncValue<AuthData>>? _authSub;

  @override
  void initState() {
    super.initState();
    final data = ref.read(authProvider).valueOrNull;
    if (data is AuthOtpSentData) _otpRequestId = data.otpRequestId;

    _authSub = ref.listenManual<AsyncValue<AuthData>>(authProvider, (prev, next) {
      if (next is AsyncError) {
        if (!mounted) return;
        final err = next.error;
        setState(() => _errorMessage = err.toString());
        _clearBoxes();
        if (err is AuthErrorInfo) {
          if (_kOtpBlockedCodes.contains(err.code) && !_blockedPopupShown) {
            _blockedPopupShown = true;
            OtpBlockedPopup.show(context).then((_) {
              if (mounted) _blockedPopupShown = false;
            });
          }
          if (err.retryAfterSeconds != null &&
              (err.code == 'OTP_COOLDOWN' ||
                  err.code == 'OTP_RATE_LIMIT_PHONE' ||
                  err.code == 'OTP_RATE_LIMIT_IP')) {
            _resendCooldown = err.retryAfterSeconds!;
            _startResendCountdown();
          }
        }
      } else if (next is AsyncLoading || next is AsyncData) {
        if (_errorMessage != null && mounted) {
          setState(() => _errorMessage = null);
        }
      }
    });

    _fetchResendCooldown();
    _startResendCountdown();

    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) _focusNodes.first.requestFocus();
    });
  }

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

  Future<void> _fetchResendCooldown() async {
    try {
      final response =
          await ref.read(apiClientProvider).get('/api/shared/config/otp');
      final data = response['data'] as Map<String, dynamic>?;
      final value = data?['resend_cooldown_seconds'] as int?;
      if (value != null && value > 0 && mounted) {
        setState(() {
          _resendCooldown = value;
          _resendSeconds = value;
        });
      }
    } catch (_) {
      // Stick with fallback.
    }
  }

  void _startResendCountdown() {
    _resendTimer?.cancel();
    setState(() => _resendSeconds = _resendCooldown);
    _resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (!mounted) {
        timer.cancel();
        return;
      }
      setState(() {
        if (_resendSeconds > 0) _resendSeconds--;
        if (_resendSeconds <= 0) timer.cancel();
      });
    });
  }

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

  void _clearBoxes({bool refocusFirst = true}) {
    for (final c in _controllers) {
      c.clear();
    }
    _autoSubmitted = false;
    if (refocusFirst && mounted) _focusNodes.first.requestFocus();
  }

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

    final code = _readCode();
    if (code.length == _kOtpLength && !_autoSubmitted && _otpRequestId != null) {
      _autoSubmitted = true;
      ref.read(authProvider.notifier).verifyOtp(_otpRequestId!, code);
    }
  }

  Future<void> _resend() async {
    if (_resendSeconds > 0) return;
    _clearBoxes();
    await ref.read(authProvider.notifier).requestOtp(widget.phone);
    if (!mounted) return;
    final next = ref.read(authProvider).valueOrNull;
    if (next is AuthOtpSentData) _otpRequestId = next.otpRequestId;
    _startResendCountdown();
  }

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

    final data = authState.valueOrNull;
    if (data is AuthOtpSentData) _otpRequestId = data.otpRequestId;

    return Scaffold(
      appBar: AppBar(title: const Text('Masukkan OTP')),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(HaloSpacing.s24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text(
                'Kode OTP telah dikirim ke ${widget.phone}',
                style: const TextStyle(
                  fontFamily: HaloTokens.fontBody,
                  fontSize: 15,
                  color: HaloTokens.inkSoft,
                ),
              ),
              const SizedBox(height: HaloSpacing.s32),
              LayoutBuilder(
                builder: (ctx, constraints) {
                  // 6 boxes laid out across the row. Tighter spacing than the
                  // legacy 4-box layout (Figma reference) so the form still
                  // fits a 320pt-wide screen.
                  const gap = HaloSpacing.s8;
                  final boxWidth =
                      (constraints.maxWidth - gap * (_kOtpLength - 1)) /
                          _kOtpLength;
                  return Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: List.generate(_kOtpLength, (i) {
                      return Padding(
                        padding: EdgeInsets.only(
                          right: i == _kOtpLength - 1 ? 0 : gap,
                        ),
                        child: _buildBox(i, boxWidth),
                      );
                    }),
                  );
                },
              ),
              const SizedBox(height: HaloSpacing.s12),
              if (_errorMessage != null)
                Text(
                  _errorMessage!,
                  textAlign: TextAlign.center,
                  style: const TextStyle(
                    fontFamily: HaloTokens.fontBody,
                    color: HaloTokens.danger,
                    fontSize: 13,
                  ),
                ),
              const SizedBox(height: HaloSpacing.s12),
              if (isLoading)
                const Center(
                  child: Padding(
                    padding:
                        EdgeInsets.symmetric(vertical: HaloSpacing.s8),
                    child: CircularProgressIndicator(),
                  ),
                ),
              const SizedBox(height: HaloSpacing.s16),
              _buildResendRow(),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildBox(int index, double width) {
    return SizedBox(
      width: width,
      height: 56,
      child: Focus(
        canRequestFocus: false,
        onKeyEvent: (node, 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;
        },
        child: TextField(
          controller: _controllers[index],
          focusNode: _focusNodes[index],
          autofocus: index == 0,
          keyboardType: TextInputType.number,
          textAlign: TextAlign.center,
          maxLength: 1,
          style: const TextStyle(
            fontFamily: HaloTokens.fontDisplay,
            fontSize: 22,
            fontWeight: FontWeight.w700,
            color: HaloTokens.ink,
          ),
          inputFormatters: [FilteringTextInputFormatter.digitsOnly],
          decoration: InputDecoration(
            counterText: '',
            contentPadding: EdgeInsets.zero,
            filled: true,
            fillColor: HaloTokens.surface,
            enabledBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(12),
              borderSide: const BorderSide(color: HaloTokens.border, width: 1.5),
            ),
            focusedBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(12),
              borderSide: const BorderSide(color: HaloTokens.brand, width: 2),
            ),
          ),
          onChanged: (v) => _onDigitChanged(index, v),
        ),
      ),
    );
  }

  Widget _buildResendRow() {
    final canResend = _resendSeconds <= 0;
    return Center(
      child: canResend
          ? GestureDetector(
              onTap: _resend,
              child: const Text(
                'Kirim ulang kode',
                style: TextStyle(
                  fontFamily: HaloTokens.fontBody,
                  color: HaloTokens.brandDark,
                  fontWeight: FontWeight.w600,
                  decoration: TextDecoration.underline,
                ),
              ),
            )
          : Text(
              'Kirim ulang dalam ${formatCountdown(_resendSeconds)}',
              style: const TextStyle(
                fontFamily: HaloTokens.fontBody,
                color: HaloTokens.inkMuted,
              ),
            ),
    );
  }
}
