import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'package:unionflow_mobile_apps/features/backup/presentation/bloc/backup_bloc.dart';
import 'package:unionflow_mobile_apps/features/backup/data/repositories/backup_repository.dart';
import 'package:unionflow_mobile_apps/features/backup/data/models/backup_model.dart';
import 'package:unionflow_mobile_apps/features/backup/data/models/backup_config_model.dart';

@GenerateMocks([BackupRepository])
import 'backup_bloc_test.mocks.dart';

void main() {
  late BackupBloc bloc;
  late MockBackupRepository mockRepository;

  // ── Fixtures ──────────────────────────────────────────────────────────────

  BackupModel fakeBackup({String id = 'bk-1', String name = 'Test'}) => BackupModel(
        id: id,
        name: name,
        type: 'MANUAL',
        status: 'COMPLETED',
        sizeBytes: 1024,
        sizeFormatted: '1 KB',
      );

  BackupConfigModel fakeConfig() => const BackupConfigModel(
        autoBackupEnabled: true,
        frequency: 'DAILY',
        retentionDays: 30,
        totalBackups: 5,
      );

  setUp(() {
    mockRepository = MockBackupRepository();
    bloc = BackupBloc(mockRepository);
  });

  tearDown(() => bloc.close());

  // ── Initial state ─────────────────────────────────────────────────────────

  test('initial state is BackupInitial', () {
    expect(bloc.state, isA<BackupInitial>());
  });

  // ── LoadBackups ───────────────────────────────────────────────────────────

  group('LoadBackups', () {
    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupsLoaded] on success',
      build: () {
        when(mockRepository.getAll())
            .thenAnswer((_) async => [fakeBackup()]);
        return bloc;
      },
      act: (b) => b.add(LoadBackups()),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupsLoaded>().having(
          (s) => s.backups.length,
          'backups.length',
          1,
        ),
      ],
      verify: (_) => verify(mockRepository.getAll()).called(1),
    );

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupError] on failure',
      build: () {
        when(mockRepository.getAll()).thenThrow(Exception('network error'));
        return bloc;
      },
      act: (b) => b.add(LoadBackups()),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupError>().having(
          (s) => s.error,
          'error',
          contains('Erreur'),
        ),
      ],
    );

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupsLoaded] with empty list',
      build: () {
        when(mockRepository.getAll()).thenAnswer((_) async => []);
        return bloc;
      },
      act: (b) => b.add(LoadBackups()),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupsLoaded>().having((s) => s.backups, 'backups', isEmpty),
      ],
    );
  });

  // ── CreateBackup ──────────────────────────────────────────────────────────

  group('CreateBackup', () {
    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupsLoaded, BackupSuccess] on success',
      build: () {
        when(mockRepository.create('Ma sauvegarde', description: anyNamed('description')))
            .thenAnswer((_) async => fakeBackup());
        when(mockRepository.getAll())
            .thenAnswer((_) async => [fakeBackup()]);
        return bloc;
      },
      act: (b) => b.add(CreateBackup('Ma sauvegarde', description: 'desc')),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupsLoaded>(),
        isA<BackupSuccess>().having(
          (s) => s.message,
          'message',
          'Sauvegarde créée',
        ),
      ],
      verify: (_) {
        verify(mockRepository.create('Ma sauvegarde', description: 'desc')).called(1);
        verify(mockRepository.getAll()).called(1);
      },
    );

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupError] on create failure',
      build: () {
        when(mockRepository.create(any, description: anyNamed('description')))
            .thenThrow(Exception('Server error'));
        return bloc;
      },
      act: (b) => b.add(CreateBackup('Test')),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupError>(),
      ],
    );
  });

  // ── RestoreBackup ─────────────────────────────────────────────────────────

  group('RestoreBackup', () {
    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupSuccess] on restore success',
      build: () {
        when(mockRepository.restore('bk-1')).thenAnswer((_) async {});
        return bloc;
      },
      act: (b) => b.add(RestoreBackup('bk-1')),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupSuccess>().having(
          (s) => s.message,
          'message',
          'Restauration en cours',
        ),
      ],
      verify: (_) => verify(mockRepository.restore('bk-1')).called(1),
    );

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupError] on restore failure',
      build: () {
        when(mockRepository.restore(any)).thenThrow(Exception('restore failed'));
        return bloc;
      },
      act: (b) => b.add(RestoreBackup('bk-1')),
      expect: () => [isA<BackupLoading>(), isA<BackupError>()],
    );
  });

  // ── DeleteBackup ──────────────────────────────────────────────────────────

  group('DeleteBackup', () {
    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupsLoaded, BackupSuccess] on delete success',
      build: () {
        when(mockRepository.delete('bk-1')).thenAnswer((_) async {});
        when(mockRepository.getAll()).thenAnswer((_) async => []);
        return bloc;
      },
      act: (b) => b.add(DeleteBackup('bk-1')),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupsLoaded>(),
        isA<BackupSuccess>().having(
          (s) => s.message,
          'message',
          'Sauvegarde supprimée',
        ),
      ],
    );

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupError] on delete failure',
      build: () {
        when(mockRepository.delete(any)).thenThrow(Exception('delete failed'));
        return bloc;
      },
      act: (b) => b.add(DeleteBackup('bk-1')),
      expect: () => [isA<BackupLoading>(), isA<BackupError>()],
    );
  });

  // ── LoadBackupConfig ──────────────────────────────────────────────────────

  group('LoadBackupConfig', () {
    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupConfigLoaded] on success',
      build: () {
        when(mockRepository.getConfig()).thenAnswer((_) async => fakeConfig());
        return bloc;
      },
      act: (b) => b.add(LoadBackupConfig()),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupConfigLoaded>().having(
          (s) => s.config.autoBackupEnabled,
          'autoBackupEnabled',
          true,
        ),
      ],
    );

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupError] on config load failure',
      build: () {
        when(mockRepository.getConfig()).thenThrow(Exception('config error'));
        return bloc;
      },
      act: (b) => b.add(LoadBackupConfig()),
      expect: () => [isA<BackupLoading>(), isA<BackupError>()],
    );
  });

  // ── UpdateBackupConfig ────────────────────────────────────────────────────

  group('UpdateBackupConfig', () {
    final configMap = <String, dynamic>{'autoBackupEnabled': false, 'frequency': 'WEEKLY'};

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupConfigLoaded, BackupSuccess] on success',
      build: () {
        when(mockRepository.updateConfig(configMap))
            .thenAnswer((_) async => const BackupConfigModel(autoBackupEnabled: false, frequency: 'WEEKLY'));
        return bloc;
      },
      act: (b) => b.add(UpdateBackupConfig(configMap)),
      expect: () => [
        isA<BackupLoading>(),
        isA<BackupConfigLoaded>(),
        isA<BackupSuccess>().having(
          (s) => s.message,
          'message',
          'Configuration mise à jour',
        ),
      ],
    );

    blocTest<BackupBloc, BackupState>(
      'emits [BackupLoading, BackupError] on update failure',
      build: () {
        when(mockRepository.updateConfig(any)).thenThrow(Exception('update error'));
        return bloc;
      },
      act: (b) => b.add(UpdateBackupConfig({})),
      expect: () => [isA<BackupLoading>(), isA<BackupError>()],
    );
  });
}
