import 'dart:async';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:bloc/bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/sign_in_with_phone_arguments.dart';
import '../../domain/arguments/verify_otp_arguments.dart';
import '../../domain/usecases/sign_in_with_phone_usecase.dart';
import '../../domain/usecases/verify_otp_usecase.dart';
import 'auth_event.dart';
import 'auth_state.dart';

/// BLoC responsible for handling authentication logic.
class AuthBloc extends Bloc<AuthEvent, AuthState>
    with BlocErrorHandler<AuthState>
    implements Disposable {
  /// The use case for signing in with a phone number.
  final SignInWithPhoneUseCase _signInUseCase;

  /// The use case for verifying an OTP.
  final VerifyOtpUseCase _verifyOtpUseCase;
  int _requestToken = 0;
  DateTime? _lastCodeRequestAt;
  DateTime? _cooldownUntil;
  static const Duration _resendCooldown = Duration(seconds: 31);
  Timer? _cooldownTimer;

  /// Creates an [AuthBloc].
  AuthBloc({
    required SignInWithPhoneUseCase signInUseCase,
    required VerifyOtpUseCase verifyOtpUseCase,
  }) : _signInUseCase = signInUseCase,
       _verifyOtpUseCase = verifyOtpUseCase,
       super(const AuthState()) {
    on<AuthSignInRequested>(_onSignInRequested);
    on<AuthOtpSubmitted>(_onOtpSubmitted);
    on<AuthErrorCleared>(_onErrorCleared);
    on<AuthOtpUpdated>(_onOtpUpdated);
    on<AuthPhoneUpdated>(_onPhoneUpdated);
    on<AuthResetRequested>(_onResetRequested);
    on<AuthCooldownTicked>(_onCooldownTicked);
  }

  /// Clears any authentication error from the state.
  void _onErrorCleared(AuthErrorCleared event, Emitter<AuthState> emit) {
    emit(state.copyWith(status: AuthStatus.codeSent, errorMessage: ''));
  }

  /// Updates the internal OTP state without triggering a submission.
  void _onOtpUpdated(AuthOtpUpdated event, Emitter<AuthState> emit) {
    emit(
      state.copyWith(
        otp: event.otp,
        status: AuthStatus.codeSent,
        errorMessage: '',
      ),
    );
  }

  /// Updates the internal phone number state without triggering a submission.
  void _onPhoneUpdated(AuthPhoneUpdated event, Emitter<AuthState> emit) {
    emit(state.copyWith(phoneNumber: event.phoneNumber, errorMessage: ''));
  }

  /// Resets the authentication state to initial for a given mode.
  void _onResetRequested(AuthResetRequested event, Emitter<AuthState> emit) {
    _requestToken++;
    _signInUseCase.cancelPending();
    _cancelCooldownTimer();
    emit(AuthState(status: AuthStatus.initial, mode: event.mode));
  }

  /// Handles the sign-in request, initiating the phone authentication process.
  Future<void> _onSignInRequested(
    AuthSignInRequested event,
    Emitter<AuthState> emit,
  ) async {
    final DateTime now = DateTime.now();
    if (_lastCodeRequestAt != null) {
      final DateTime cooldownUntil =
          _cooldownUntil ?? _lastCodeRequestAt!.add(_resendCooldown);
      final int remaining = cooldownUntil.difference(now).inSeconds;
      if (remaining > 0) {
        _startCooldown(remaining);
        emit(
          state.copyWith(
            status: AuthStatus.error,
            mode: event.mode,
            phoneNumber: event.phoneNumber ?? state.phoneNumber,
            errorMessage:
                'Please wait ${remaining}s before requesting a new code.',
            cooldownSecondsRemaining: remaining,
          ),
        );
        return;
      }
    }

    _signInUseCase.cancelPending();
    final int token = ++_requestToken;
    _lastCodeRequestAt = now;
    _cooldownUntil = now.add(_resendCooldown);
    _cancelCooldownTimer();
    emit(
      state.copyWith(
        status: AuthStatus.loading,
        mode: event.mode,
        phoneNumber: event.phoneNumber,
        cooldownSecondsRemaining: 0,
      ),
    );

    await handleError(
      emit: emit,
      action: () async {
        final String? verificationId = await _signInUseCase(
          SignInWithPhoneArguments(
            phoneNumber: event.phoneNumber ?? state.phoneNumber,
          ),
        );
        if (token != _requestToken) return;
        emit(
          state.copyWith(
            status: AuthStatus.codeSent,
            verificationId: verificationId,
            cooldownSecondsRemaining: 0,
          ),
        );
      },
      onError: (String errorKey) {
        if (token != _requestToken) return state;
        return state.copyWith(
          status: AuthStatus.error,
          errorMessage: errorKey,
          cooldownSecondsRemaining: 0,
        );
      },
    );
  }

  void _onCooldownTicked(
    AuthCooldownTicked event,
    Emitter<AuthState> emit,
  ) {
    if (event.secondsRemaining <= 0) {
      _cancelCooldownTimer();
      _cooldownUntil = null;
      emit(
        state.copyWith(
          status: AuthStatus.initial,
          errorMessage: '',
          cooldownSecondsRemaining: 0,
        ),
      );
      return;
    }

    emit(
      state.copyWith(
        status: AuthStatus.error,
        errorMessage:
            'Please wait ${event.secondsRemaining}s before requesting a new code.',
        cooldownSecondsRemaining: event.secondsRemaining,
      ),
    );
  }

  void _startCooldown(int secondsRemaining) {
    _cancelCooldownTimer();
    int remaining = secondsRemaining;
    add(AuthCooldownTicked(remaining));
    _cooldownTimer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
      remaining -= 1;
      if (remaining <= 0) {
        timer.cancel();
        _cooldownTimer = null;
        add(const AuthCooldownTicked(0));
        return;
      }
      add(AuthCooldownTicked(remaining));
    });
  }

  void _cancelCooldownTimer() {
    _cooldownTimer?.cancel();
    _cooldownTimer = null;
  }

  /// Handles OTP submission and verification.
  Future<void> _onOtpSubmitted(
    AuthOtpSubmitted event,
    Emitter<AuthState> emit,
  ) async {
    emit(state.copyWith(status: AuthStatus.loading));
    await handleError(
      emit: emit,
      action: () async {
        final User? user = await _verifyOtpUseCase(
          VerifyOtpArguments(
            verificationId: event.verificationId,
            smsCode: event.smsCode,
            mode: event.mode,
          ),
        );
        emit(state.copyWith(status: AuthStatus.authenticated, user: user));
      },
      onError: (String errorKey) => state.copyWith(
        status: AuthStatus.error,
        errorMessage: errorKey,
      ),
    );
  }

  /// Disposes the BLoC resources.
  @override
  void dispose() {
    _cancelCooldownTimer();
    close();
  }
}

