/// Tests unitaires pour RetryPolicy
library retry_policy_test;

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:unionflow_mobile_apps/core/network/retry_policy.dart';

void main() {
  group('RetryPolicy', () {
    late RetryPolicy retryPolicy;

    setUp(() {
      retryPolicy = RetryPolicy(
        config: const RetryConfig(
          maxAttempts: 3,
          initialDelayMs: 100, // Short delay for tests
          maxDelayMs: 1000,
          backoffMultiplier: 2.0,
          useJitter: false, // Disable jitter for predictable tests
        ),
      );
    });

    group('execute - Happy path', () {
      test('should return result when operation succeeds on first attempt', () async {
        // Arrange
        const expectedResult = 'success';
        int attempts = 0;

        Future<String> operation() async {
          attempts++;
          return expectedResult;
        }

        // Act
        final result = await retryPolicy.execute(operation: operation);

        // Assert
        expect(result, equals(expectedResult));
        expect(attempts, equals(1));
      });

      test('should retry and succeed on second attempt', () async {
        // Arrange
        const expectedResult = 'success';
        int attempts = 0;

        Future<String> operation() async {
          attempts++;
          if (attempts == 1) {
            throw Exception('Temporary failure');
          }
          return expectedResult;
        }

        // Act
        final result = await retryPolicy.execute(
          operation: operation,
          shouldRetry: (_) => true,
        );

        // Assert
        expect(result, equals(expectedResult));
        expect(attempts, equals(2));
      });

      test('should retry maximum attempts and succeed on last attempt', () async {
        // Arrange
        const expectedResult = 'success';
        int attempts = 0;

        Future<String> operation() async {
          attempts++;
          if (attempts < 3) {
            throw Exception('Temporary failure');
          }
          return expectedResult;
        }

        // Act
        final result = await retryPolicy.execute(
          operation: operation,
          shouldRetry: (_) => true,
        );

        // Assert
        expect(result, equals(expectedResult));
        expect(attempts, equals(3));
      });
    });

    group('execute - Retry exhaustion', () {
      test('should throw error when all retries are exhausted', () async {
        // Arrange
        int attempts = 0;

        Future<String> operation() async {
          attempts++;
          throw Exception('Persistent failure');
        }

        // Act & Assert
        expect(
          () => retryPolicy.execute(
            operation: operation,
            shouldRetry: (_) => true,
          ),
          throwsA(isA<Exception>()),
        );

        // Wait for all attempts
        await Future.delayed(const Duration(milliseconds: 500));
        expect(attempts, equals(3)); // maxAttempts
      });

      test('should not retry when shouldRetry returns false', () async {
        // Arrange
        int attempts = 0;

        Future<String> operation() async {
          attempts++;
          throw Exception('Non-retryable error');
        }

        // Act & Assert
        expect(
          () => retryPolicy.execute(
            operation: operation,
            shouldRetry: (_) => false,
          ),
          throwsA(isA<Exception>()),
        );

        expect(attempts, equals(1)); // No retries
      });
    });

    group('execute - Error classification', () {
      test('should retry timeout exceptions by default', () async {
        // Arrange
        int attempts = 0;

        Future<String> operation() async {
          attempts++;
          if (attempts == 1) {
            throw TimeoutException('Request timeout');
          }
          return 'success';
        }

        // Act
        final result = await retryPolicy.execute(operation: operation);

        // Assert
        expect(result, equals('success'));
        expect(attempts, equals(2));
      });

      test('should use custom shouldRetry function', () async {
        // Arrange
        int attempts = 0;
        final specificError = Exception('Specific retryable error');

        Future<String> operation() async {
          attempts++;
          if (attempts == 1) {
            throw specificError;
          }
          return 'success';
        }

        bool customShouldRetry(dynamic error) {
          return error == specificError;
        }

        // Act
        final result = await retryPolicy.execute(
          operation: operation,
          shouldRetry: customShouldRetry,
        );

        // Assert
        expect(result, equals('success'));
        expect(attempts, equals(2));
      });
    });

    group('execute - Retry callback', () {
      test('should call onRetry callback with attempt details', () async {
        // Arrange
        int attempts = 0;
        final retryCallbacks = <Map<String, dynamic>>[];

        Future<String> operation() async {
          attempts++;
          if (attempts < 3) {
            throw Exception('Temporary failure');
          }
          return 'success';
        }

        void onRetry(int attempt, dynamic error, Duration delay) {
          retryCallbacks.add({
            'attempt': attempt,
            'error': error,
            'delay': delay,
          });
        }

        // Act
        final result = await retryPolicy.execute(
          operation: operation,
          shouldRetry: (_) => true,
          onRetry: onRetry,
        );

        // Assert
        expect(result, equals('success'));
        expect(retryCallbacks.length, equals(2)); // 2 retries before success

        // Verify first retry callback
        expect(retryCallbacks[0]['attempt'], equals(1));
        expect(retryCallbacks[0]['error'], isA<Exception>());
        expect(retryCallbacks[0]['delay'], isA<Duration>());

        // Verify second retry callback
        expect(retryCallbacks[1]['attempt'], equals(2));
        expect(retryCallbacks[1]['error'], isA<Exception>());
        expect(retryCallbacks[1]['delay'], isA<Duration>());
      });
    });

    group('RetryConfig', () {
      test('should use default configuration values', () {
        // Arrange & Act
        const config = RetryConfig();

        // Assert
        expect(config.maxAttempts, equals(3));
        expect(config.initialDelayMs, equals(1000));
        expect(config.maxDelayMs, equals(30000));
        expect(config.backoffMultiplier, equals(2.0));
        expect(config.useJitter, equals(true));
      });

      test('should use critical configuration preset', () {
        // Arrange & Act
        const config = RetryConfig.critical;

        // Assert
        expect(config.maxAttempts, equals(5));
        expect(config.initialDelayMs, equals(500));
        expect(config.maxDelayMs, equals(60000));
      });

      test('should use background sync configuration preset', () {
        // Arrange & Act
        const config = RetryConfig.backgroundSync;

        // Assert
        expect(config.maxAttempts, equals(10));
        expect(config.initialDelayMs, equals(2000));
        expect(config.maxDelayMs, equals(120000));
      });
    });

    group('RetryExtension', () {
      test('should add withRetry method to Future functions', () async {
        // Arrange
        int attempts = 0;

        Future<String> operation() async {
          attempts++;
          if (attempts == 1) {
            throw Exception('First attempt fails');
          }
          return 'success';
        }

        // Act
        final result = await operation.withRetry(
          config: const RetryConfig(
            maxAttempts: 3,
            initialDelayMs: 100,
            useJitter: false,
          ),
          shouldRetry: (_) => true,
        );

        // Assert
        expect(result, equals('success'));
        expect(attempts, equals(2));
      });
    });
  });
}
