import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gametime/application/application.dart';
import 'package:gametime/domain/domain.dart';
import 'package:gametime/presentation/presentation.dart';

void main() {
  testWidgets('la validation au moins une mesure active bloque la sauvegarde', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository();
    final exerciseUseCases = _exerciseUseCases(exerciseRepository);

    await tester.pumpWidget(
      MaterialApp(
        home: ExerciseFormScreen(
          exerciseUseCases: exerciseUseCases,
          mediaUseCases: _mediaUseCases(exerciseRepository),
        ),
      ),
    );

    await tester.enterText(find.byType(TextFormField).first, 'Gainage');
    await tester.dragUntilVisible(
      find.widgetWithText(SwitchListTile, 'Temps'),
      find.byType(ListView),
      const Offset(0, -200),
    );
    await tester.pumpAndSettle();
    await tester.tap(find.widgetWithText(SwitchListTile, 'Temps'));
    await tester.pump();
    await tester.dragUntilVisible(
      find.text('Enregistrer'),
      find.byType(ListView),
      const Offset(0, -200),
    );
    await tester.pumpAndSettle();
    await tester.tap(find.text('Enregistrer'));
    await tester.pump();

    expect(
      find.text('Active au moins une mesure pour enregistrer.'),
      findsOneWidget,
    );
    expect(exerciseRepository.saved, isEmpty);
  });

  testWidgets('la liste affiche les badges des mesures disponibles', (
    tester,
  ) async {
    final exercise = Exercise(
      metadata: _metadata('exercise-1'),
      name: 'Squat',
      description: 'Jambes · charge libre',
      hasTimeMeasure: true,
      hasRepsMeasure: true,
      hasScoreMeasure: true,
      scoreLabel: 'Charge',
      scoreUnit: 'kg',
      defaultTargetTimeSeconds: 45,
      defaultTargetReps: 10,
      defaultTargetScore: 80,
      category: ExerciseCategory.finishing,
      isExample: true,
    );

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(body: ExerciseListTile(exercise: exercise)),
      ),
    );

    expect(find.text('Squat'), findsOneWidget);
    expect(find.text('Jambes · charge libre'), findsOneWidget);
    expect(find.text('Temps'), findsOneWidget);
    expect(find.text('Répétitions'), findsOneWidget);
    expect(find.text('Score (kg)'), findsOneWidget);
    expect(find.text('Finition'), findsOneWidget);
    expect(find.text('Exemple'), findsOneWidget);
  });

  testWidgets('ajouter un tag à un exercice le sauvegarde normalisé', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ExerciseFormScreen(
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          mediaUseCases: _mediaUseCases(exerciseRepository),
          tagSuggestions: const ['match'],
        ),
      ),
    );

    await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Tirs');
    await tester.tap(find.text('Ajouter un tag'));
    await tester.pumpAndSettle();
    await tester.enterText(
      find.widgetWithText(TextField, 'Nom du tag'),
      ' Match ',
    );
    await tester.tap(find.widgetWithText(FilledButton, 'Ajouter'));
    await tester.pumpAndSettle();

    expect(find.widgetWithText(Chip, 'match'), findsOneWidget);

    await tester.dragUntilVisible(
      find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
      find.byType(ListView),
      const Offset(0, -200),
    );
    await tester.pumpAndSettle();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
      '30',
    );
    await tester.dragUntilVisible(
      find.text('Enregistrer'),
      find.byType(ListView),
      const Offset(0, -200),
    );
    await tester.pumpAndSettle();
    await tester.tap(find.text('Enregistrer'));
    await tester.pumpAndSettle();

    expect(exerciseRepository.saved.single.tags, ['match']);
  });

  testWidgets('filtre les exercices par tag et efface tous les filtres', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository()
      ..exercises.addAll([
        Exercise(
          metadata: _metadata('exercise-1'),
          name: 'Tirs match',
          hasTimeMeasure: true,
          hasRepsMeasure: false,
          hasScoreMeasure: false,
          tags: const ['match', 'extérieur'],
        ),
        Exercise(
          metadata: _metadata('exercise-2'),
          name: 'Dribble routine',
          hasTimeMeasure: false,
          hasRepsMeasure: true,
          hasScoreMeasure: false,
          tags: const ['routine'],
        ),
      ]);

    await tester.pumpWidget(
      MaterialApp(
        home: ExerciseLibraryScreen(
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          mediaUseCases: _mediaUseCases(exerciseRepository),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

    await tester.tap(find.widgetWithText(FilterChip, 'match'));
    await tester.pump();

    expect(find.text('Tirs match'), findsOneWidget);
    expect(find.text('Dribble routine'), findsNothing);
    expect(find.text('Effacer les filtres'), findsOneWidget);

    await tester.tap(find.text('Effacer les filtres'));
    await tester.pump();

    expect(find.text('Tirs match'), findsOneWidget);
    expect(find.text('Dribble routine'), findsOneWidget);
  });

  testWidgets('basculer en chrono intégré masque l’unité et affiche le badge', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.binding.setSurfaceSize(const Size(400, 2400));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await tester.pumpWidget(
      MaterialApp(
        home: ExerciseFormScreen(
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          mediaUseCases: _mediaUseCases(exerciseRepository),
        ),
      ),
    );

    await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Run');
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
      '30',
    );
    await tester.tap(find.widgetWithText(SwitchListTile, 'Score'));
    await tester.pump();

    expect(find.widgetWithText(TextFormField, 'Unité'), findsOneWidget);

    await tester.tap(find.text('Chrono intégré'));
    await tester.pump();

    expect(find.text('Temps réalisé'), findsOneWidget);
    expect(find.widgetWithText(TextFormField, 'Unité'), findsNothing);

    await tester.tap(find.text('Enregistrer'));
    await tester.pumpAndSettle();

    final saved = exerciseRepository.saved.single;
    expect(saved.scoreInputMode, ScoreInputMode.stopwatch);
    expect(saved.defaultTargetTimeSeconds, 30);
    expect(saved.defaultTargetScoreTimeMs, isNull);

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(body: ExerciseListTile(exercise: saved)),
      ),
    );
    await tester.pumpAndSettle();

    expect(find.text('Score chrono'), findsOneWidget);
    expect(find.text('Score'), findsNothing);
  });

  testWidgets(
    'sélectionner une image déclenche l’import et confirme le fichier',
    (tester) async {
      final exerciseRepository = _FakeExerciseRepository();
      final storage = _FakeMediaStorage();

      await tester.pumpWidget(
        MaterialApp(
          home: ExerciseFormScreen(
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
            mediaUseCases: _mediaUseCases(exerciseRepository, storage: storage),
            mediaPicker: _FakeMediaPicker(imagePath: '/tmp/imports/photo.png'),
          ),
        ),
      );

      await tester.tap(find.text('Ajouter une image'));
      await tester.pump();

      expect(storage.imports.single.sourcePath, '/tmp/imports/photo.png');
      expect(storage.imports.single.kind, MediaKind.image);
      expect(find.text('photo.png'), findsOneWidget);
      expect(find.text('Image ajoutée.'), findsOneWidget);
    },
  );

  testWidgets('ajouter des images bloque la sixième image', (tester) async {
    final exerciseRepository = _FakeExerciseRepository();
    final storage = _FakeMediaStorage();

    await tester.binding.setSurfaceSize(const Size(400, 1200));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await tester.pumpWidget(
      MaterialApp(
        home: ExerciseFormScreen(
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          mediaUseCases: _mediaUseCases(exerciseRepository, storage: storage),
          mediaPicker: _FakeMediaPicker(
            imagePaths: const [
              '/tmp/imports/photo-1.png',
              '/tmp/imports/photo-2.png',
              '/tmp/imports/photo-3.png',
              '/tmp/imports/photo-4.png',
              '/tmp/imports/photo-5.png',
              '/tmp/imports/photo-6.png',
            ],
          ),
        ),
      ),
    );

    for (var i = 0; i < 5; i++) {
      await tester.tap(find.text('Ajouter une image'));
      await tester.pump();
    }

    expect(storage.imports, hasLength(5));
    expect(find.text('photo-5.png'), findsOneWidget);
    expect(find.text('Maximum 5 images par exercice.'), findsOneWidget);

    await tester.tap(find.text('Ajouter une image'));
    await tester.pump();

    expect(storage.imports, hasLength(5));
    expect(find.text('photo-6.png'), findsNothing);
    expect(find.text('Maximum 5 images par exercice.'), findsWidgets);
  });

  testWidgets('supprimer une image la retire de la galerie', (tester) async {
    final exerciseRepository = _FakeExerciseRepository();
    final storage = _FakeMediaStorage();

    await tester.pumpWidget(
      MaterialApp(
        home: ExerciseFormScreen(
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          mediaUseCases: _mediaUseCases(exerciseRepository, storage: storage),
          mediaPicker: _FakeMediaPicker(imagePath: '/tmp/imports/photo.png'),
        ),
      ),
    );

    await tester.tap(find.text('Ajouter une image'));
    await tester.pump();

    expect(find.text('photo.png'), findsOneWidget);

    await tester.tap(find.byTooltip('Supprimer l’image'));
    await tester.pump();

    expect(find.text('photo.png'), findsNothing);
    expect(find.text('Aucune image sélectionnée'), findsOneWidget);
  });

  testWidgets('activer les étapes permet d’ajouter une étape temps', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.binding.setSurfaceSize(const Size(400, 3200));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await _pumpExerciseForm(tester, exerciseRepository);
    await tester.tap(
      find.widgetWithText(
        SwitchListTile,
        'Rythmer cet exercice avec des étapes',
      ),
    );
    await tester.pump();
    await tester.tap(find.text('Ajouter une étape'));
    await tester.pump();

    await tester.enterText(
      find.widgetWithText(TextFormField, 'Nom de l’étape').last,
      'Dribble main droite',
    );
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Durée par défaut (s)').last,
      '12',
    );
    await tester.pump();

    expect(find.text('1. Dribble main droite'), findsOneWidget);
    expect(find.text('Temps · 12 s'), findsOneWidget);
  });

  testWidgets('ajouter une étape répétitions avec score manuel', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.binding.setSurfaceSize(const Size(400, 3600));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await _pumpExerciseForm(tester, exerciseRepository);
    await tester.tap(
      find.widgetWithText(
        SwitchListTile,
        'Rythmer cet exercice avec des étapes',
      ),
    );
    await tester.pump();
    await tester.tap(find.text('Ajouter une étape'));
    await tester.pump();
    await tester.tap(_stepTypeRadio('Répétitions').last);
    await tester.pump();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Nom de l’étape').last,
      'Pompes',
    );
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Répétitions par défaut').last,
      '10',
    );
    await tester.tap(find.widgetWithText(SwitchListTile, 'Score d’étape'));
    await tester.pump();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Score à saisir').last,
      'Réussites',
    );
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Unité').last,
      'paniers',
    );
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Score par défaut').last,
      '0',
    );
    await tester.pump();

    expect(find.text('1. Pompes'), findsOneWidget);
    expect(find.text('Répétitions · 10'), findsOneWidget);
    expect(find.text('Mode de score'), findsOneWidget);
  });

  testWidgets('réordonner les étapes via Monter Descendre', (tester) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.binding.setSurfaceSize(const Size(400, 4200));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await _pumpExerciseForm(tester, exerciseRepository);
    await tester.tap(
      find.widgetWithText(
        SwitchListTile,
        'Rythmer cet exercice avec des étapes',
      ),
    );
    await tester.pump();
    await tester.tap(find.text('Ajouter une étape'));
    await tester.pump();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Nom de l’étape').at(0),
      'Première',
    );
    await tester.tap(find.text('Ajouter une étape'));
    await tester.pump();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Nom de l’étape').at(1),
      'Deuxième',
    );
    await tester.pump();

    await tester.tap(find.byTooltip('Actions de l’étape 2'));
    await tester.pumpAndSettle();
    await tester.tap(find.text('Monter'));
    await tester.pumpAndSettle();

    expect(find.text('1. Deuxième'), findsOneWidget);
    expect(find.text('2. Première'), findsOneWidget);
  });

  testWidgets('supprimer une étape demande confirmation', (tester) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.binding.setSurfaceSize(const Size(400, 3200));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await _pumpExerciseForm(tester, exerciseRepository);
    await tester.tap(
      find.widgetWithText(
        SwitchListTile,
        'Rythmer cet exercice avec des étapes',
      ),
    );
    await tester.pump();
    await tester.tap(find.text('Ajouter une étape'));
    await tester.pump();

    await tester.tap(find.byTooltip('Actions de l’étape 1'));
    await tester.pumpAndSettle();
    await tester.tap(find.text('Supprimer'));
    await tester.pumpAndSettle();

    expect(find.text('Supprimer cette étape ?'), findsOneWidget);

    await tester.tap(find.widgetWithText(FilledButton, 'Supprimer'));
    await tester.pumpAndSettle();

    expect(find.text('Nouvelle étape'), findsNothing);
  });

  testWidgets('la limite de 8 étapes bloque l’ajout d’une neuvième', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.binding.setSurfaceSize(const Size(400, 8000));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await _pumpExerciseForm(tester, exerciseRepository);
    await tester.tap(
      find.widgetWithText(
        SwitchListTile,
        'Rythmer cet exercice avec des étapes',
      ),
    );
    await tester.pump();
    for (var index = 0; index < 8; index++) {
      await tester.tap(find.text('Ajouter une étape'));
      await tester.pump();
    }

    expect(find.text('Limite atteinte'), findsOneWidget);
    expect(
      find.text('Un exercice peut contenir jusqu’à 8 étapes.'),
      findsOneWidget,
    );
    expect(find.text('Ajouter une étape'), findsNothing);
  });

  testWidgets('sauvegarder transmet les étapes au use case', (tester) async {
    final exerciseRepository = _FakeExerciseRepository();

    await tester.binding.setSurfaceSize(const Size(400, 3600));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await _pumpExerciseForm(tester, exerciseRepository);
    await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Combo');
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
      '30',
    );
    await tester.tap(
      find.widgetWithText(
        SwitchListTile,
        'Rythmer cet exercice avec des étapes',
      ),
    );
    await tester.pump();
    await tester.tap(find.text('Ajouter une étape'));
    await tester.pump();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Nom de l’étape').last,
      'Dribble main droite',
    );
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Durée par défaut (s)').last,
      '12',
    );

    await tester.ensureVisible(find.text('Enregistrer'));
    await tester.tap(find.text('Enregistrer'));
    await tester.pumpAndSettle();

    final saved = exerciseRepository.saved.single;
    expect(saved.steps, hasLength(1));
    expect(saved.steps.single.id, isNot(startsWith('step-draft-')));
    expect(saved.steps.single.name, 'Dribble main droite');
    expect(saved.steps.single.type, ExerciseStepType.time);
    expect(saved.steps.single.defaultTargetValue, 12);
  });

  testWidgets(
    'le réglage d’enchaînement automatique des étapes est sauvegardé',
    (tester) async {
      final exerciseRepository = _FakeExerciseRepository();

      await tester.binding.setSurfaceSize(const Size(400, 3600));
      addTearDown(() => tester.binding.setSurfaceSize(null));

      await _pumpExerciseForm(tester, exerciseRepository);
      await tester.enterText(
        find.widgetWithText(TextFormField, 'Nom'),
        'Combo',
      );
      await tester.enterText(
        find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
        '30',
      );
      await tester.tap(
        find.widgetWithText(
          SwitchListTile,
          'Rythmer cet exercice avec des étapes',
        ),
      );
      await tester.pump();
      await tester.tap(
        find.widgetWithText(
          SwitchListTile,
          'Enchaîner automatiquement les chronos consécutifs',
        ),
      );
      await tester.pump();
      await tester.tap(find.text('Ajouter une étape'));
      await tester.pump();
      await tester.enterText(
        find.widgetWithText(TextFormField, 'Nom de l’étape').last,
        'Dribble main droite',
      );
      await tester.enterText(
        find.widgetWithText(TextFormField, 'Durée par défaut (s)').last,
        '12',
      );

      await tester.ensureVisible(find.text('Enregistrer'));
      await tester.tap(find.text('Enregistrer'));
      await tester.pumpAndSettle();

      final saved = exerciseRepository.saved.single;
      expect(saved.steps, hasLength(1));
      expect(saved.autoStartNextTimedStep, isFalse);
    },
  );

  testWidgets('désigner une image comme icône l’indique visuellement', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository();
    final storage = _FakeMediaStorage();

    await tester.binding.setSurfaceSize(const Size(400, 1600));
    addTearDown(() => tester.binding.setSurfaceSize(null));

    await tester.pumpWidget(
      MaterialApp(
        home: ExerciseFormScreen(
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          mediaUseCases: _mediaUseCases(exerciseRepository, storage: storage),
          mediaPicker: _FakeMediaPicker(
            imagePaths: const [
              '/tmp/imports/photo-1.png',
              '/tmp/imports/photo-2.png',
            ],
          ),
        ),
      ),
    );

    await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Squat');
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Temps par défaut (s)'),
      '45',
    );
    await tester.tap(find.text('Ajouter une image'));
    await tester.pump();
    await tester.tap(find.text('Ajouter une image'));
    await tester.pump();

    await tester.tap(find.byTooltip('Définir comme icône').last);
    await tester.pump();

    expect(find.byTooltip('Icône choisie'), findsOneWidget);

    await tester.tap(find.text('Enregistrer'));
    await tester.pumpAndSettle();

    expect(exerciseRepository.saved.single.iconMediaId, 'id-2');
  });

  testWidgets('ExerciseListTile utilise l’icône choisie', (tester) async {
    final exercise = Exercise(
      metadata: _metadata('exercise-1'),
      name: 'Squat',
      imageMediaIds: const ['image-1', 'image-2'],
      iconMediaId: 'image-2',
      hasTimeMeasure: true,
      hasRepsMeasure: false,
      hasScoreMeasure: false,
      defaultTargetTimeSeconds: 45,
    );

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(body: ExerciseListTile(exercise: exercise)),
      ),
    );

    expect(find.byTooltip('Icône d’exercice image-2'), findsOneWidget);
  });

  testWidgets(
    'supprimer un exercice demande confirmation puis le retire de la liste',
    (tester) async {
      final exerciseRepository = _FakeExerciseRepository()
        ..referenced = true
        ..exercises.add(
          Exercise(
            metadata: _metadata('exercise-1'),
            name: 'Squat',
            hasTimeMeasure: true,
            hasRepsMeasure: false,
            hasScoreMeasure: false,
            defaultTargetTimeSeconds: 45,
          ),
        );

      await tester.pumpWidget(
        MaterialApp(
          home: ExerciseLibraryScreen(
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
            mediaUseCases: _mediaUseCases(exerciseRepository),
          ),
        ),
      );
      await tester.pump();
      await tester.pump();

      await tester.tap(find.byTooltip('Supprimer l’exercice'));
      await tester.pumpAndSettle();

      expect(find.text('Supprimer Squat ?'), findsOneWidget);
      expect(
        find.textContaining('Les programmes existants restent inchangés.'),
        findsOneWidget,
      );

      await tester.tap(find.text('Annuler'));
      await tester.pumpAndSettle();

      expect(find.text('Squat'), findsOneWidget);
      expect(exerciseRepository.saved, isEmpty);

      await tester.tap(find.byTooltip('Supprimer l’exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.widgetWithText(FilledButton, 'Supprimer'));
      await tester.pumpAndSettle();

      expect(find.text('Squat'), findsNothing);
      expect(exerciseRepository.saved.single.metadata.deletedAt, isNotNull);
    },
  );
}

Future<void> _pumpExerciseForm(
  WidgetTester tester,
  _FakeExerciseRepository exerciseRepository,
) {
  return tester.pumpWidget(
    MaterialApp(
      home: ExerciseFormScreen(
        exerciseUseCases: _exerciseUseCases(exerciseRepository),
        mediaUseCases: _mediaUseCases(exerciseRepository),
      ),
    ),
  );
}

Finder _stepTypeRadio(String title) {
  return find.byWidgetPredicate(
    (widget) =>
        widget is RadioListTile<ExerciseStepType> &&
        widget.title is Text &&
        (widget.title as Text).data == title,
    description: 'RadioListTile<ExerciseStepType> "$title"',
  );
}

ExerciseUseCases _exerciseUseCases(_FakeExerciseRepository repository) {
  return ExerciseUseCases(
    repository: repository,
    programRepository: _FakeProgramRepository(),
    clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
    ids: _FakeIds(),
    originDeviceId: 'device-1',
  );
}

final class _FakeProgramRepository implements ProgramRepository {
  @override
  Future<Program?> findById(String id) async => null;

  @override
  Future<List<Program>> listActive() async => const [];

  @override
  Future<void> replaceExercises(Program program, DateTime deletedAt) async {}

  @override
  Future<void> save(Program program) async {}

  @override
  Future<void> saveExercise(ProgramExercise exercise) async {}
}

MediaUseCases _mediaUseCases(
  _FakeExerciseRepository exerciseRepository, {
  _FakeMediaStorage? storage,
}) {
  return MediaUseCases(
    mediaRepository: _FakeMediaAssetRepository(),
    exerciseRepository: exerciseRepository,
    storage: storage ?? _FakeMediaStorage(),
    clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
    ids: _FakeIds(),
    originDeviceId: 'device-1',
  );
}

final class _FakeMediaPicker implements MediaSourcePicker {
  _FakeMediaPicker({this.imagePath, this.imagePaths = const []});

  final String? imagePath;
  final List<String> imagePaths;
  var _nextImagePath = 0;

  @override
  Future<String?> pickPath(MediaKind kind) async {
    return switch (kind) {
      MediaKind.image => _pickImagePath(),
      MediaKind.video => null,
    };
  }

  String? _pickImagePath() {
    if (imagePaths.isEmpty) {
      return imagePath;
    }
    if (_nextImagePath >= imagePaths.length) {
      return null;
    }
    final path = imagePaths[_nextImagePath];
    _nextImagePath += 1;
    return path;
  }
}

EntityMetadata _metadata(String id) {
  return EntityMetadata(
    id: id,
    createdAt: DateTime.utc(2026, 7, 17),
    updatedAt: DateTime.utc(2026, 7, 17),
    originDeviceId: 'device-1',
  );
}

final class _FakeClock implements Clock {
  const _FakeClock(this.value);

  final DateTime value;

  @override
  DateTime now() => value;
}

final class _FakeIds implements IdGenerator {
  var _next = 0;

  @override
  String newId() {
    _next += 1;
    return 'id-$_next';
  }
}

final class _FakeExerciseRepository implements ExerciseRepository {
  final exercises = <Exercise>[];
  final saved = <Exercise>[];
  var referenced = false;

  @override
  Future<Exercise?> findById(String id) async {
    for (final exercise in exercises) {
      if (exercise.metadata.id == id) {
        return exercise;
      }
    }
    return null;
  }

  @override
  Future<bool> isReferencedByProgram(String id) async => referenced;

  @override
  Future<List<Exercise>> listActive() async {
    return exercises
        .where((exercise) => exercise.metadata.deletedAt == null)
        .toList();
  }

  @override
  Future<void> save(Exercise exercise) async {
    saved.add(exercise);
    exercises.removeWhere((item) => item.metadata.id == exercise.metadata.id);
    exercises.add(exercise);
  }
}

final class _FakeMediaAssetRepository implements MediaAssetRepository {
  final saved = <MediaAsset>[];

  @override
  Future<MediaAsset?> findById(String id) async {
    for (final asset in saved) {
      if (asset.metadata.id == id) {
        return asset;
      }
    }
    return null;
  }

  @override
  Future<List<MediaAsset>> listActive() async => saved;

  @override
  Future<void> save(MediaAsset mediaAsset) async {
    saved.add(mediaAsset);
  }
}

final class _FakeMediaStorage implements LocalMediaStorage {
  final imports = <_MediaImportCall>[];

  @override
  Future<void> deleteByLocalUri(String localUri) async {}

  @override
  Future<StoredMediaFile> importFile({
    required String sourcePath,
    required MediaKind kind,
    required String stableFileName,
  }) async {
    imports.add(
      _MediaImportCall(
        sourcePath: sourcePath,
        kind: kind,
        stableFileName: stableFileName,
      ),
    );
    return StoredMediaFile(
      localUri: 'file:///tmp/$stableFileName',
      mimeType: kind == MediaKind.image ? 'image/png' : 'video/mp4',
      sizeBytes: 1,
    );
  }

  @override
  Future<Set<String>> listManagedLocalUris() async => const {};
}

final class _MediaImportCall {
  const _MediaImportCall({
    required this.sourcePath,
    required this.kind,
    required this.stableFileName,
  });

  final String sourcePath;
  final MediaKind kind;
  final String stableFileName;
}
