// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
part of 'download_queue_provider.dart';

extension _DownloadQueueVerificationGate on DownloadQueueNotifier {
  /// Completes when the app is in the foreground. Verification challenges
  /// can only be handled there: launching a browser from the background is
  /// blocked by the OS and the challenge would expire unseen.
  Future<bool> _waitForForeground(Future<void> cancellationSignal) async {
    if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
      return true;
    }
    final completer = Completer<void>();
    final listener = AppLifecycleListener(
      onResume: () {
        if (!completer.isCompleted) completer.complete();
      },
    );
    try {
      return await Future.any<bool>([
        completer.future.then((_) => true),
        cancellationSignal.then((_) => false),
      ]);
    } finally {
      listener.dispose();
    }
  }

  Future<bool> _openVerificationAndWait(String itemId, String extensionId) {
    if (_verificationWaitCoordinator.hasActiveFlow(extensionId)) {
      _log.i(
        'Joining active verification flow for $extensionId instead of opening another challenge',
      );
    }

    return _verificationWaitCoordinator.waitForGrant(
      itemId: itemId,
      service: extensionId,
      startFlow: (cancellationSignal) =>
          _runVerificationFlow(extensionId, cancellationSignal),
    );
  }

  Future<bool> _runVerificationFlow(
    String extensionId,
    Future<void> cancellationSignal,
  ) async {
    if (WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) {
      _log.i(
        'Verification required for $extensionId while app is in background; '
        'deferring challenge until the app is foregrounded',
      );
      final reachedForeground = await _waitForForeground(cancellationSignal);
      if (!reachedForeground) {
        _log.i(
          'Verification wait for $extensionId was cancelled before the app returned to the foreground',
        );
        return false;
      }
    }

    return openVerificationAndAwaitGrant(
      extensionId,
      browserMode: ref.read(settingsProvider).extensionVerificationBrowserMode,
      cancellationSignal: cancellationSignal,
    );
  }

  Future<bool> _handleVerificationRequiredDownload(
    DownloadItem item,
    String errorMsg,
    String? verificationService,
  ) async {
    final targetService = (verificationService ?? '').trim().isNotEmpty
        ? verificationService!.trim()
        : item.service;
    if (_verificationRetryGuard.hasRetriedAfterGrant(item.id, targetService)) {
      _log.e(
        'Verification was already completed once for ${item.track.name} on $targetService; not opening another challenge',
      );
      updateItemStatus(
        item.id,
        DownloadStatus.failed,
        error: errorMsg,
        errorType: DownloadErrorType.verificationRequired,
      );
      _failedInSession++;
      return true;
    }
    _log.i(
      'Download for ${item.track.name} requires verification; waiting for $targetService grant',
    );
    updateItemStatus(
      item.id,
      DownloadStatus.downloading,
      error: 'Waiting for verification',
      errorType: DownloadErrorType.verificationRequired,
    );

    try {
      await _notificationService.showVerificationRequired();
    } catch (error) {
      _log.w('Failed to show the verification-required notification: $error');
    }

    late final bool verified;
    try {
      verified = await _openVerificationAndWait(item.id, targetService);
    } finally {
      try {
        await _notificationService.cancelVerificationRequired();
      } catch (error) {
        _log.w(
          'Failed to clear the verification-required notification: $error',
        );
      }
    }
    final current = _findItemById(item.id);
    if (current == null || _isLocallyCancelled(item.id, item: current)) {
      _log.i('Verification wait stopped after item was removed or cancelled');
      return true;
    }
    if (_isPausePending(item.id)) {
      _requeueItemForPause(item.id);
      _pausePendingItemIds.remove(item.id);
      _log.i('Verification wait stopped because the queue was paused');
      return true;
    }

    // Only a completed grant consumes the automatic retry. If bootstrap or
    // browser launch failed before a challenge opened, a later attempt must
    // still be allowed after the network changes.
    _verificationRetryGuard.recordVerificationResult(
      item.id,
      targetService,
      granted: verified,
    );
    if (verified) {
      _log.i(
        'Verification complete for $targetService; retrying ${item.track.name}',
      );
      updateItemStatus(
        item.id,
        DownloadStatus.queued,
        progress: 0,
        speedMBps: 0,
        error: 'Retrying after verification',
        errorType: DownloadErrorType.verificationRequired,
      );
      _saveQueueToStorage();
      return true;
    }

    _log.e('Verification did not complete for $targetService');
    updateItemStatus(
      item.id,
      DownloadStatus.failed,
      error: errorMsg,
      errorType: DownloadErrorType.verificationRequired,
    );
    _failedInSession++;
    return true;
  }

  Duration _rateLimitBackoffDelay(String errorMsg) {
    final lower = errorMsg.toLowerCase();
    final retryAfterMatch = RegExp(
      r'retry[- ]?after(?: seconds)?[:= ]+(\d+)',
      caseSensitive: false,
    ).firstMatch(lower);
    final parsedSeconds = retryAfterMatch == null
        ? null
        : int.tryParse(retryAfterMatch.group(1) ?? '');
    final seconds = (parsedSeconds ?? 30).clamp(5, 300).toInt();
    return Duration(seconds: seconds);
  }

  Future<bool> _handleRateLimitedDownload(
    DownloadItem item,
    String errorMsg,
  ) async {
    if (_rateLimitRetriedItemIds.contains(item.id)) {
      return false;
    }
    _rateLimitRetriedItemIds.add(item.id);

    final delay = _rateLimitBackoffDelay(errorMsg);
    _log.i(
      'Rate limited while downloading ${item.track.name}; retrying after ${delay.inSeconds}s',
    );
    updateItemStatus(
      item.id,
      DownloadStatus.downloading,
      error: 'Rate limited, retrying after ${delay.inSeconds}s',
      errorType: DownloadErrorType.rateLimit,
    );

    await Future<void>.delayed(delay);
    final current = _findItemById(item.id);
    if (current == null || _isLocallyCancelled(item.id, item: current)) {
      return true;
    }
    updateItemStatus(
      item.id,
      DownloadStatus.queued,
      progress: 0,
      speedMBps: 0,
      error: 'Retrying after rate limit',
      errorType: DownloadErrorType.rateLimit,
    );
    _saveQueueToStorage();
    return true;
  }
}
