/// Tests unitaires pour OfflineManager
library offline_manager_test;

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:unionflow_mobile_apps/core/network/offline_manager.dart';
import 'package:unionflow_mobile_apps/core/storage/pending_operations_store.dart';
import 'package:unionflow_mobile_apps/core/config/environment.dart';

@GenerateMocks([
  Connectivity,
  PendingOperationsStore,
])
import 'offline_manager_test.mocks.dart';

void main() {
  // Initialize AppConfig for tests
  setUpAll(() {
    AppConfig.initialize();
  });

  group('OfflineManager', () {
    late OfflineManager offlineManager;
    late MockConnectivity mockConnectivity;
    late MockPendingOperationsStore mockOperationsStore;
    late StreamController<List<ConnectivityResult>> connectivityController;

    setUp(() {
      mockConnectivity = MockConnectivity();
      mockOperationsStore = MockPendingOperationsStore();
      connectivityController = StreamController<List<ConnectivityResult>>.broadcast();

      // Setup default stubs
      when(mockConnectivity.onConnectivityChanged)
          .thenAnswer((_) => connectivityController.stream);
      when(mockConnectivity.checkConnectivity())
          .thenAnswer((_) async => [ConnectivityResult.wifi]);
      when(mockOperationsStore.getPendingOperations())
          .thenAnswer((_) async => []);
      when(mockOperationsStore.addPendingOperation(
        operationType: anyNamed('operationType'),
        endpoint: anyNamed('endpoint'),
        data: anyNamed('data'),
        headers: anyNamed('headers'),
      )).thenAnswer((_) async => Future.value());

      offlineManager = OfflineManager(mockConnectivity, mockOperationsStore);
    });

    tearDown(() {
      connectivityController.close();
      offlineManager.dispose();
    });

    group('Connectivity status', () {
      test('should initialize with unknown status', () {
        // Assert
        expect(offlineManager.currentStatus, equals(ConnectivityStatus.unknown));
      });

      test('should detect online status when WiFi is connected', () async {
        // Arrange
        when(mockConnectivity.checkConnectivity())
            .thenAnswer((_) async => [ConnectivityResult.wifi]);

        // Act
        final isOnline = await offlineManager.isOnline;

        // Assert
        expect(isOnline, isTrue);
      });

      test('should detect online status when mobile is connected', () async {
        // Arrange
        when(mockConnectivity.checkConnectivity())
            .thenAnswer((_) async => [ConnectivityResult.mobile]);

        // Act
        final isOnline = await offlineManager.isOnline;

        // Assert
        expect(isOnline, isTrue);
      });

      test('should detect offline status when no connectivity', () async {
        // Arrange
        when(mockConnectivity.checkConnectivity())
            .thenAnswer((_) async => [ConnectivityResult.none]);

        // Act
        final isOnline = await offlineManager.isOnline;

        // Assert
        expect(isOnline, isFalse);
      });
    });

    group('Status stream', () {
      test('should emit online status when WiFi connects', () async {
        // Arrange
        final statusUpdates = <ConnectivityStatus>[];
        offlineManager.statusStream.listen(statusUpdates.add);

        // Act
        connectivityController.add([ConnectivityResult.wifi]);
        await Future.delayed(const Duration(milliseconds: 100));

        // Assert
        expect(statusUpdates, contains(ConnectivityStatus.online));
      });

      test('should emit offline status when connection is lost', () async {
        // Arrange
        final statusUpdates = <ConnectivityStatus>[];
        offlineManager.statusStream.listen(statusUpdates.add);

        // Act - First connect, then disconnect
        connectivityController.add([ConnectivityResult.wifi]);
        await Future.delayed(const Duration(milliseconds: 100));

        connectivityController.add([ConnectivityResult.none]);
        await Future.delayed(const Duration(milliseconds: 100));

        // Assert
        expect(statusUpdates, contains(ConnectivityStatus.online));
        expect(statusUpdates, contains(ConnectivityStatus.offline));
      });

      test('should not emit duplicate status updates', () async {
        // Arrange
        final statusUpdates = <ConnectivityStatus>[];
        offlineManager.statusStream.listen(statusUpdates.add);

        // Act - Send same status multiple times
        connectivityController.add([ConnectivityResult.wifi]);
        await Future.delayed(const Duration(milliseconds: 100));

        connectivityController.add([ConnectivityResult.wifi]);
        await Future.delayed(const Duration(milliseconds: 100));

        connectivityController.add([ConnectivityResult.wifi]);
        await Future.delayed(const Duration(milliseconds: 100));

        // Assert - Should only emit once
        expect(statusUpdates.where((s) => s == ConnectivityStatus.online).length, equals(1));
      });
    });

    group('Operation queueing', () {
      test('should queue operation when offline', () async {
        // Arrange
        const operationType = 'approveTransaction';
        const endpoint = '/api/finance/approvals/123/approve';
        final data = {'approvalId': '123', 'comment': 'Approved'};

        // Act
        await offlineManager.queueOperation(
          operationType: operationType,
          endpoint: endpoint,
          data: data,
        );

        // Assert
        verify(mockOperationsStore.addPendingOperation(
          operationType: operationType,
          endpoint: endpoint,
          data: data,
          headers: null,
        )).called(1);
      });

      test('should include headers when queueing operation', () async {
        // Arrange
        const operationType = 'createBudget';
        const endpoint = '/api/finance/budgets';
        final data = {'name': 'Test Budget'};
        final headers = {'Authorization': 'Bearer token123'};

        // Act
        await offlineManager.queueOperation(
          operationType: operationType,
          endpoint: endpoint,
          data: data,
          headers: headers,
        );

        // Assert
        verify(mockOperationsStore.addPendingOperation(
          operationType: operationType,
          endpoint: endpoint,
          data: data,
          headers: headers,
        )).called(1);
      });

      test('should get count of pending operations', () async {
        // Arrange
        when(mockOperationsStore.getPendingOperations()).thenAnswer(
          (_) async => [
            {'id': '1', 'operationType': 'approve'},
            {'id': '2', 'operationType': 'reject'},
          ],
        );

        // Act
        final count = await offlineManager.getPendingOperationsCount();

        // Assert
        expect(count, equals(2));
      });
    });

    group('Clear operations', () {
      test('should clear all pending operations', () async {
        // Arrange
        when(mockOperationsStore.clearAll()).thenAnswer((_) async => Future.value());

        // Act
        await offlineManager.clearPendingOperations();

        // Assert
        verify(mockOperationsStore.clearAll()).called(1);
      });
    });

    group('Retry pending operations', () {
      test('should not retry when offline', () async {
        // Arrange
        when(mockConnectivity.checkConnectivity())
            .thenAnswer((_) async => [ConnectivityResult.none]);
        connectivityController.add([ConnectivityResult.none]);
        await Future.delayed(const Duration(milliseconds: 100));

        // Act
        await offlineManager.retryPendingOperations();

        // Assert
        verifyNever(mockOperationsStore.getPendingOperations());
      });

      test('should process pending operations when manually triggered and online', () async {
        // Arrange
        when(mockConnectivity.checkConnectivity())
            .thenAnswer((_) async => [ConnectivityResult.wifi]);
        connectivityController.add([ConnectivityResult.wifi]);
        await Future.delayed(const Duration(milliseconds: 100));

        when(mockOperationsStore.getPendingOperations()).thenAnswer(
          (_) async => [
            {
              'id': '1',
              'operationType': 'approve',
              'endpoint': '/api/finance/approvals/123/approve',
              'data': {},
            },
          ],
        );

        // Act
        await offlineManager.retryPendingOperations();

        // Assert
        verify(mockOperationsStore.getPendingOperations()).called(1);
      });
    });
  });
}
