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';

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

const Color _kAccentPink = Color(0xFFBE7C8A);
const Color _kBoxBorder = Color(0xFFE0E0E0);

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;

  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;

    // Register the auth listener ONCE — must NOT live in build(), or the
    // resend countdown's setState will pile up duplicate listeners every
    // second and the error toast will fire many times per state change.
    _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 the server says we're rate-limited, extend the resend countdown
        // to match — disables "Kirim ulang kode" until the lockout clears.
        if (err is AuthErrorInfo &&
            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) {
    // Move forward when a digit is entered, back when cleared.
    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;
      // Keep keyboard open during verify — dismissing it caused a Scaffold
      // layout shift mid-snackbar-animation, which made the error toast
      // visually duplicate.
      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: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Kode OTP telah dikirim ke ${widget.phone}'),
            const SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: List.generate(_kOtpLength, _buildBox),
            ),
            const SizedBox(height: 12),
            if (_errorMessage != null)
              Text(
                _errorMessage!,
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.red.shade700, fontSize: 13),
              ),
            const SizedBox(height: 12),
            if (isLoading)
              const Center(
                child: Padding(
                  padding: EdgeInsets.symmetric(vertical: 8),
                  child: CircularProgressIndicator(),
                ),
              ),
            const SizedBox(height: 16),
            _buildResendRow(),
          ],
        ),
      ),
    );
  }

  Widget _buildBox(int index) {
    return SizedBox(
      width: 48,
      height: 56,
      // Wrap with Focus to intercept hardware backspace BEFORE the TextField:
      // when the current box is empty, TextField.onChanged doesn't fire on
      // backspace, so we'd be stuck. We catch it here and rewind one box.
      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(fontSize: 24, fontWeight: FontWeight.w600),
          inputFormatters: [FilteringTextInputFormatter.digitsOnly],
          decoration: InputDecoration(
            counterText: '',
            contentPadding: EdgeInsets.zero,
            enabledBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(12),
              borderSide: const BorderSide(color: _kBoxBorder, width: 1.5),
            ),
            focusedBorder: OutlineInputBorder(
              borderRadius: BorderRadius.circular(12),
              borderSide: const BorderSide(color: _kAccentPink, 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(
                  color: _kAccentPink,
                  fontWeight: FontWeight.w600,
                  decoration: TextDecoration.underline,
                ),
              ),
            )
          : Text(
              'Kirim ulang dalam ${formatCountdown(_resendSeconds)}',
              style: TextStyle(color: Colors.grey.shade600),
            ),
    );
  }
}
