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 liste affiche le badge exemple des programmes seedés', (
    tester,
  ) async {
    final programRepository = _FakeProgramRepository()
      ..programs.add(
        Program(
          metadata: _metadata('program-1'),
          name: 'Fondations basket - 45 min',
          defaultRestSeconds: 45,
          isExample: true,
        ),
      );
    final exerciseRepository = _FakeExerciseRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramListScreen(
          programUseCases: _programUseCases(
            programRepository,
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

    expect(find.text('Fondations basket - 45 min'), findsOneWidget);
    expect(find.text('Exemple'), findsOneWidget);
    expect(find.byTooltip('Actions'), findsOneWidget);
  });

  testWidgets('la liste programmes vide conserve le message dédié', (
    tester,
  ) async {
    final programRepository = _FakeProgramRepository();
    final exerciseRepository = _FakeExerciseRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramListScreen(
          programUseCases: _programUseCases(
            programRepository,
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

    expect(find.text('Aucun programme'), findsOneWidget);
    expect(
      find.text('Assemble quelques exercices pour préparer une séance.'),
      findsOneWidget,
    );
  });

  testWidgets('filtre les programmes par tag et peut effacer les filtres', (
    tester,
  ) async {
    final programRepository = _FakeProgramRepository()
      ..programs.addAll([
        Program(
          metadata: _metadata('program-1'),
          name: 'Programme match',
          defaultRestSeconds: 45,
          tags: const ['match', 'intense'],
        ),
        Program(
          metadata: _metadata('program-2'),
          name: 'Programme routine',
          defaultRestSeconds: 30,
          tags: const ['routine'],
        ),
      ]);
    final exerciseRepository = _FakeExerciseRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramListScreen(
          programUseCases: _programUseCases(
            programRepository,
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

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

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

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

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

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

  testWidgets('dupliquer un programme ouvre la copie en édition', (
    tester,
  ) async {
    final programRepository = _FakeProgramRepository()
      ..programs.add(
        Program(
          metadata: _metadata('program-1'),
          name: 'Jambes',
          defaultRestSeconds: 60,
          tags: const ['match'],
        ),
      );
    final exerciseRepository = _FakeExerciseRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramListScreen(
          programUseCases: _programUseCases(
            programRepository,
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

    await tester.tap(find.byTooltip('Actions'));
    await tester.pumpAndSettle();
    await tester.tap(find.text('Dupliquer'));
    await tester.pumpAndSettle();

    expect(find.text('Modifier le programme'), findsOneWidget);
    expect(find.text('Copie de Jambes'), findsOneWidget);
    expect(
      find.text("Programme dupliqué. Ajuste la copie avant de l'utiliser."),
      findsOneWidget,
    );
    expect(find.widgetWithText(Chip, 'match'), findsOneWidget);
  });

  testWidgets('modifier un programme exemple conserve sa provenance', (
    tester,
  ) async {
    final programRepository = _FakeProgramRepository()
      ..programs.add(
        Program(
          metadata: _metadata('program-1'),
          name: 'Fondations basket - 45 min',
          defaultRestSeconds: 45,
          isExample: true,
        ),
      );
    final exerciseRepository = _FakeExerciseRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramFormScreen(
          programUseCases: _programUseCases(
            programRepository,
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          program: programRepository.programs.single,
        ),
      ),
    );

    await tester.enterText(find.widgetWithText(TextFormField, 'Nom'), 'Edité');
    await tester.tap(find.text('Enregistrer'));
    await tester.pumpAndSettle();

    expect(programRepository.programs.single.name, 'Edité');
    expect(programRepository.programs.single.isExample, isTrue);
  });

  testWidgets(
    'ajoute un exercice au programme avec sa configuration par défaut',
    (tester) async {
      final programRepository = _FakeProgramRepository();
      final exerciseRepository = _FakeExerciseRepository()
        ..exercises.add(
          Exercise(
            metadata: _metadata('exercise-1'),
            name: 'Squat',
            hasTimeMeasure: true,
            hasRepsMeasure: true,
            hasScoreMeasure: false,
            defaultTargetTimeSeconds: 45,
            defaultTargetReps: 10,
          ),
        );

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramFormScreen(
            programUseCases: _programUseCases(
              programRepository,
              exerciseRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
          ),
        ),
      );

      await tester.enterText(
        find.widgetWithText(TextFormField, 'Nom'),
        'Jambes',
      );
      await tester.tap(find.text('Ajouter un exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Squat'));
      await tester.pumpAndSettle();

      expect(find.text('Squat'), findsOneWidget);
      expect(find.text('Repos 60 s'), findsOneWidget);
      expect(find.text('Exercice ajouté'), findsOneWidget);
      expect(find.byTooltip("Personnaliser l'exercice"), findsOneWidget);
      expect(find.byType(CheckboxListTile), findsNothing);

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

      final savedExercise = programRepository.programs.single.exercises.single;
      expect(savedExercise.setsCount, 3);
      expect(savedExercise.timeEnabled, isTrue);
      expect(savedExercise.repsEnabled, isTrue);
      expect(savedExercise.scoreEnabled, isFalse);
      expect(savedExercise.targetTimeSeconds, 45);
      expect(savedExercise.targetReps, 10);
      expect(savedExercise.restSecondsOverride, 60);
    },
  );

  testWidgets(
    'limite les mesures aux mesures autorisées par l’exercice source',
    (tester) async {
      final exerciseRepository = _FakeExerciseRepository()
        ..exercises.add(
          Exercise(
            metadata: _metadata('exercise-1'),
            name: 'Pompes',
            hasTimeMeasure: false,
            hasRepsMeasure: true,
            hasScoreMeasure: false,
            defaultTargetReps: 12,
          ),
        );

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramFormScreen(
            programUseCases: _programUseCases(
              _FakeProgramRepository(),
              exerciseRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
          ),
        ),
      );

      await tester.tap(find.text('Ajouter un exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Pompes'));
      await tester.pumpAndSettle();

      await tester.tap(find.byTooltip("Personnaliser l'exercice"));
      await tester.pumpAndSettle();

      expect(find.text("Personnaliser l'exercice"), findsOneWidget);
      expect(
        find.widgetWithText(SwitchListTile, 'Répétitions'),
        findsOneWidget,
      );
      expect(find.widgetWithText(SwitchListTile, 'Temps'), findsNothing);
      expect(find.widgetWithText(SwitchListTile, 'Score'), findsNothing);
    },
  );

  testWidgets(
    'la popup exercice ajouté disparaît et se ferme à la personnalisation',
    (tester) async {
      final exerciseRepository = _FakeExerciseRepository()
        ..exercises.add(
          Exercise(
            metadata: _metadata('exercise-1'),
            name: 'Squat',
            hasTimeMeasure: true,
            hasRepsMeasure: false,
            hasScoreMeasure: false,
            defaultTargetTimeSeconds: 45,
          ),
        );

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramFormScreen(
            programUseCases: _programUseCases(
              _FakeProgramRepository(),
              exerciseRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
          ),
        ),
      );

      await tester.tap(find.text('Ajouter un exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Squat'));
      await tester.pumpAndSettle();

      expect(find.text('Exercice ajouté'), findsOneWidget);

      for (var i = 0; i < 5; i++) {
        await tester.pump(const Duration(seconds: 1));
      }
      await tester.pump(const Duration(milliseconds: 500));

      expect(find.text('Exercice ajouté'), findsNothing);

      await tester.tap(find.text('Ajouter un exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Squat').first);
      await tester.pumpAndSettle();

      expect(find.text('Exercice ajouté'), findsOneWidget);

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

      expect(find.text('Exercice ajouté'), findsNothing);
      expect(find.text("Personnaliser l'exercice"), findsOneWidget);
    },
  );

  testWidgets(
    'ajouter un exercice avec étapes conserve le snapshot des étapes',
    (tester) async {
      final steps = [
        _step(
          id: 'step-1',
          position: 0,
          name: 'Course',
          type: ExerciseStepType.time,
        ),
        _step(
          id: 'step-2',
          position: 1,
          name: 'Tirs',
          type: ExerciseStepType.reps,
        ),
      ];
      final programRepository = _FakeProgramRepository();
      final exerciseRepository = _FakeExerciseRepository()
        ..exercises.add(
          Exercise(
            metadata: _metadata('exercise-steps'),
            name: 'Circuit tirs',
            hasTimeMeasure: false,
            hasRepsMeasure: true,
            hasScoreMeasure: false,
            defaultTargetReps: 2,
            steps: steps,
          ),
        );

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramFormScreen(
            programUseCases: _programUseCases(
              programRepository,
              exerciseRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
          ),
        ),
      );

      await tester.enterText(
        find.widgetWithText(TextFormField, 'Nom'),
        'Programme étapes',
      );
      await tester.tap(find.text('Ajouter un exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Circuit tirs'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Enregistrer'));
      await tester.pumpAndSettle();

      final savedSteps = programRepository
          .programs
          .single
          .exercises
          .single
          .exerciseStepsSnapshot;
      expect(savedSteps, hasLength(2));
      expect(savedSteps.map((step) => step.name), ['Course', 'Tirs']);
      expect(savedSteps.map((step) => step.type), [
        ExerciseStepType.time,
        ExerciseStepType.reps,
      ]);
    },
  );

  testWidgets(
    'réenregistrer un programme existant conserve les étapes du snapshot',
    (tester) async {
      final steps = [
        _step(
          id: 'step-1',
          position: 0,
          name: 'Course',
          type: ExerciseStepType.time,
        ),
        _step(
          id: 'step-2',
          position: 1,
          name: 'Tirs',
          type: ExerciseStepType.reps,
        ),
      ];
      final programRepository = _FakeProgramRepository()
        ..programs.add(
          Program(
            metadata: _metadata('program-steps'),
            name: 'Programme étapes',
            defaultRestSeconds: 45,
            exercises: [
              ProgramExercise(
                metadata: _metadata('program-exercise-steps'),
                programId: 'program-steps',
                sourceExerciseId: 'exercise-steps',
                position: 0,
                exerciseNameSnapshot: 'Circuit tirs',
                exerciseStepsSnapshot: steps,
                availableTimeSnapshot: false,
                availableRepsSnapshot: true,
                availableScoreSnapshot: false,
                setsCount: 3,
                timeEnabled: false,
                repsEnabled: true,
                scoreEnabled: false,
                targetReps: 2,
              ),
            ],
          ),
        );
      final exerciseRepository = _FakeExerciseRepository();

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramFormScreen(
            programUseCases: _programUseCases(
              programRepository,
              exerciseRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
            program: programRepository.programs.single,
          ),
        ),
      );

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

      final savedSteps = programRepository
          .programs
          .single
          .exercises
          .single
          .exerciseStepsSnapshot;
      expect(savedSteps, hasLength(2));
      expect(savedSteps.map((step) => step.name), ['Course', 'Tirs']);
      expect(savedSteps.map((step) => step.position), [0, 1]);
    },
  );

  testWidgets(
    'personnaliser l’enchaînement automatique transmet l’override au programme',
    (tester) async {
      final steps = [
        _step(
          id: 'step-1',
          position: 0,
          name: 'Course',
          type: ExerciseStepType.time,
        ),
        _step(
          id: 'step-2',
          position: 1,
          name: 'Retour',
          type: ExerciseStepType.time,
        ),
      ];
      final programRepository = _FakeProgramRepository();
      final exerciseRepository = _FakeExerciseRepository()
        ..exercises.add(
          Exercise(
            metadata: _metadata('exercise-steps'),
            name: 'Navettes',
            hasTimeMeasure: true,
            hasRepsMeasure: false,
            hasScoreMeasure: false,
            defaultTargetTimeSeconds: 30,
            steps: steps,
          ),
        );

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

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramFormScreen(
            programUseCases: _programUseCases(
              programRepository,
              exerciseRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
          ),
        ),
      );

      await tester.enterText(
        find.widgetWithText(TextFormField, 'Nom'),
        'Programme étapes',
      );
      await tester.tap(find.text('Ajouter un exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Navettes'));
      await tester.pumpAndSettle();
      await tester.tap(find.byTooltip("Personnaliser l'exercice"));
      await tester.pumpAndSettle();

      await tester.tap(
        find.widgetWithText(
          SwitchListTile,
          'Enchaîner automatiquement les chronos consécutifs',
        ),
      );
      await tester.pump();
      expect(
        find.textContaining('Personnalisé pour ce programme'),
        findsOneWidget,
      );

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

      final savedExercise = programRepository.programs.single.exercises.single;
      expect(savedExercise.autoStartNextTimedStepSnapshot, isTrue);
      expect(savedExercise.autoStartNextTimedStepOverride, isFalse);
    },
  );

  testWidgets(
    'revenir au réglage de l’exercice remet l’override programme à null',
    (tester) async {
      final steps = [
        _step(
          id: 'step-1',
          position: 0,
          name: 'Course',
          type: ExerciseStepType.time,
        ),
      ];
      final programRepository = _FakeProgramRepository();
      final exerciseRepository = _FakeExerciseRepository()
        ..exercises.add(
          Exercise(
            metadata: _metadata('exercise-steps'),
            name: 'Navettes',
            hasTimeMeasure: true,
            hasRepsMeasure: false,
            hasScoreMeasure: false,
            defaultTargetTimeSeconds: 30,
            autoStartNextTimedStep: false,
            steps: steps,
          ),
        );

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

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramFormScreen(
            programUseCases: _programUseCases(
              programRepository,
              exerciseRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
          ),
        ),
      );

      await tester.enterText(
        find.widgetWithText(TextFormField, 'Nom'),
        'Programme étapes',
      );
      await tester.tap(find.text('Ajouter un exercice'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Navettes'));
      await tester.pumpAndSettle();
      await tester.tap(find.byTooltip("Personnaliser l'exercice"));
      await tester.pumpAndSettle();

      await tester.tap(
        find.widgetWithText(
          SwitchListTile,
          'Enchaîner automatiquement les chronos consécutifs',
        ),
      );
      await tester.pump();
      expect(
        find.textContaining('Personnalisé pour ce programme'),
        findsOneWidget,
      );

      await tester.tap(find.text("Revenir au réglage de l'exercice"));
      await tester.pump();
      expect(find.textContaining("Réglage de l'exercice"), findsOneWidget);

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

      final savedExercise = programRepository.programs.single.exercises.single;
      expect(savedExercise.autoStartNextTimedStepSnapshot, isFalse);
      expect(savedExercise.autoStartNextTimedStepOverride, isNull);
    },
  );

  testWidgets('affiche l’objectif de chrono pour un score en mode chrono', (
    tester,
  ) async {
    final exerciseRepository = _FakeExerciseRepository()
      ..exercises.add(
        Exercise(
          metadata: _metadata('exercise-1'),
          name: 'Suicides',
          hasTimeMeasure: true,
          hasRepsMeasure: true,
          hasScoreMeasure: true,
          scoreInputMode: ScoreInputMode.stopwatch,
          scoreLabel: 'Temps réalisé',
          defaultTargetTimeSeconds: 30,
          defaultTargetReps: 5,
          defaultTargetScoreTimeMs: 12000,
        ),
      );

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramFormScreen(
          programUseCases: _programUseCases(
            _FakeProgramRepository(),
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
        ),
      ),
    );

    await tester.tap(find.text('Ajouter un exercice'));
    await tester.pumpAndSettle();
    await tester.tap(find.text('Suicides'));
    await tester.pumpAndSettle();
    await tester.tap(find.byTooltip("Personnaliser l'exercice"));
    await tester.pumpAndSettle();

    expect(find.text('Score chrono'), findsOneWidget);
    await tester.dragUntilVisible(
      find.text(
        "Temps sert d'objectif de durée ; Score chrono enregistre "
        'le temps réalisé.',
      ),
      find.byType(ListView),
      const Offset(0, -200),
    );
    expect(
      find.text(
        "Temps sert d'objectif de durée ; Score chrono enregistre "
        'le temps réalisé.',
      ),
      findsOneWidget,
    );
    await tester.dragUntilVisible(
      find.text('Objectif de chrono'),
      find.byType(ListView),
      const Offset(0, -200),
    );
    expect(
      find.widgetWithText(TextFormField, 'Objectif de chrono'),
      findsOneWidget,
    );
    expect(find.widgetWithText(TextFormField, 'Cible score'), findsNothing);
    expect(
      find.text('Le résultat réel sera mesuré pendant la série.'),
      findsOneWidget,
    );
  });

  testWidgets(
    'supprimer un programme demande confirmation puis le retire de la liste',
    (tester) async {
      final programRepository = _FakeProgramRepository()
        ..programs.add(
          Program(
            metadata: _metadata('program-1'),
            name: 'Jambes',
            defaultRestSeconds: 60,
          ),
        );
      final templateRepository = _FakeWorkoutTemplateRepository()
        ..templates.add(
          WorkoutTemplate(
            metadata: _metadata('template-1'),
            name: 'Séance jambes',
            programs: [
              WorkoutTemplateProgram(
                metadata: _metadata('template-program-1'),
                workoutTemplateId: 'template-1',
                sourceProgramId: 'program-1',
                position: 0,
                programNameSnapshot: 'Jambes',
                defaultRestSecondsSnapshot: 60,
                programSnapshotJson: '{"exercises":[]}',
              ),
            ],
          ),
        );
      final exerciseRepository = _FakeExerciseRepository();

      await tester.pumpWidget(
        MaterialApp(
          home: ProgramListScreen(
            programUseCases: _programUseCases(
              programRepository,
              exerciseRepository,
              templateRepository: templateRepository,
            ),
            exerciseUseCases: _exerciseUseCases(exerciseRepository),
          ),
        ),
      );
      await tester.pump();
      await tester.pump();

      await tester.tap(find.byTooltip('Actions'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Supprimer'));
      await tester.pumpAndSettle();

      expect(find.text('Supprimer Jambes ?'), findsOneWidget);
      expect(
        find.textContaining('Le supprimer le retirera de ces séances.'),
        findsOneWidget,
      );

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

      expect(find.text('Jambes'), findsOneWidget);
      expect(programRepository.saved, isEmpty);

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

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

  testWidgets('partager un programme envoie le partage avec un email valide', (
    tester,
  ) async {
    final programRepository = _FakeProgramRepository()
      ..programs.add(
        Program(
          metadata: _metadata('program-1'),
          name: 'Programme tirs',
          defaultRestSeconds: 45,
          exercises: [
            ProgramExercise(
              metadata: _metadata('program-exercise-1'),
              programId: 'program-1',
              sourceExerciseId: 'exercise-1',
              position: 0,
              exerciseNameSnapshot: 'Tirs',
              availableTimeSnapshot: false,
              availableRepsSnapshot: true,
              availableScoreSnapshot: false,
              setsCount: 3,
              timeEnabled: false,
              repsEnabled: true,
              scoreEnabled: false,
              targetReps: 10,
            ),
          ],
        ),
      );
    final exerciseRepository = _FakeExerciseRepository();
    final remoteShareApi = _FakeRemoteShareApi();

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramFormScreen(
          programUseCases: _programUseCases(
            programRepository,
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          shareUseCases: _shareUseCases(
            programRepository: programRepository,
            remoteShareApi: remoteShareApi,
          ),
          program: programRepository.programs.single,
        ),
      ),
    );

    await tester.tap(find.byTooltip('Partager'));
    await tester.pumpAndSettle();

    expect(find.text('Partager le programme'), findsOneWidget);

    await tester.enterText(
      find.widgetWithText(TextFormField, 'Emails destinataires'),
      'joueur@example.com',
    );
    await tester.tap(find.text('Envoyer le partage'));
    await tester.pump();
    await tester.pump();

    expect(remoteShareApi.sendCalls, 1);
    expect(remoteShareApi.lastResourceType, ShareResourceType.program);
    expect(remoteShareApi.lastRecipientEmails, ['joueur@example.com']);
    expect(find.text('Partage envoyé.'), findsOneWidget);
  });

  testWidgets('partager sans compte affiche l’écran compte requis', (
    tester,
  ) async {
    final programRepository = _FakeProgramRepository()
      ..programs.add(
        Program(
          metadata: _metadata('program-1'),
          name: 'Programme tirs',
          defaultRestSeconds: 45,
        ),
      );
    final exerciseRepository = _FakeExerciseRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: ProgramFormScreen(
          programUseCases: _programUseCases(
            programRepository,
            exerciseRepository,
          ),
          exerciseUseCases: _exerciseUseCases(exerciseRepository),
          shareUseCases: _shareUseCases(
            tokenStore: _FakeAuthTokenStore(),
            programRepository: programRepository,
          ),
          program: programRepository.programs.single,
        ),
      ),
    );

    await tester.tap(find.byTooltip('Partager'));
    await tester.pumpAndSettle();
    await tester.enterText(
      find.widgetWithText(TextFormField, 'Emails destinataires'),
      'joueur@example.com',
    );
    await tester.tap(find.text('Envoyer le partage'));
    await tester.pumpAndSettle();

    expect(find.text('Compte requis pour partager'), findsWidgets);
    expect(find.text('Annuler'), findsOneWidget);

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

    expect(find.text('Partager le programme'), findsOneWidget);
  });
}

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

ProgramUseCases _programUseCases(
  _FakeProgramRepository programRepository,
  _FakeExerciseRepository exerciseRepository, {
  _FakeWorkoutTemplateRepository? templateRepository,
}) {
  return ProgramUseCases(
    programRepository: programRepository,
    exerciseRepository: exerciseRepository,
    templateRepository: templateRepository ?? _FakeWorkoutTemplateRepository(),
    clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
    ids: _FakeIds(),
    originDeviceId: 'device-1',
  );
}

ShareUseCases _shareUseCases({
  _FakeAuthTokenStore? tokenStore,
  _FakeRemoteShareApi? remoteShareApi,
  _FakeProgramRepository? programRepository,
  _FakeWorkoutTemplateRepository? templateRepository,
}) {
  return ShareUseCases(
    tokenStore: tokenStore ?? _FakeAuthTokenStore('token-1'),
    remoteShareApi: remoteShareApi ?? _FakeRemoteShareApi(),
    inboxRepository: _FakeShareInboxRepository(),
    pendingActionRepository: _FakePendingShareActionRepository(),
    localChanges: _FakeLocalSyncChangeRepository(),
    programRepository: programRepository ?? _FakeProgramRepository(),
    templateRepository: templateRepository ?? _FakeWorkoutTemplateRepository(),
    clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
    ids: _FakeIds(),
    originDeviceId: 'device-1',
  );
}

final class _FakeWorkoutTemplateRepository
    implements WorkoutTemplateRepository {
  final templates = <WorkoutTemplate>[];

  @override
  Future<WorkoutTemplate?> findById(String id) async => null;

  @override
  Future<List<WorkoutTemplate>> listActive() async {
    return templates
        .where((template) => template.metadata.deletedAt == null)
        .toList();
  }

  @override
  Future<void> replaceComposition(
    WorkoutTemplate template,
    DateTime deletedAt,
  ) async {
    templates.removeWhere((item) => item.metadata.id == template.metadata.id);
    templates.add(template);
  }

  @override
  Future<void> save(WorkoutTemplate template) async {
    templates.removeWhere((item) => item.metadata.id == template.metadata.id);
    templates.add(template);
  }

  @override
  Future<void> saveOverride(WorkoutTemplateExerciseOverride override) async {}

  @override
  Future<void> saveProgram(WorkoutTemplateProgram program) async {}
}

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

ExerciseStep _step({
  required String id,
  required int position,
  required String name,
  required ExerciseStepType type,
}) {
  return ExerciseStep(
    id: id,
    position: position,
    name: name,
    type: type,
    defaultTargetValue: type == ExerciseStepType.time ? 30 : 5,
  );
}

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>[];

  @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 => false;

  @override
  Future<List<Exercise>> listActive() async => exercises;

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

final class _FakeProgramRepository implements ProgramRepository {
  final programs = <Program>[];
  final saved = <Program>[];

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

  @override
  Future<List<Program>> listActive() async {
    return programs
        .where((program) => program.metadata.deletedAt == null)
        .toList();
  }

  @override
  Future<void> replaceExercises(Program program, DateTime deletedAt) async {
    programs.removeWhere((item) => item.metadata.id == program.metadata.id);
    programs.add(program);
  }

  @override
  Future<void> save(Program program) async {
    saved.add(program);
    programs.removeWhere((item) => item.metadata.id == program.metadata.id);
    programs.add(program);
  }

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

final class _FakeAuthTokenStore implements AuthTokenStore {
  _FakeAuthTokenStore([this.token]);

  String? token;

  @override
  Future<void> clearToken() async {
    token = null;
  }

  @override
  Future<String?> readToken() async => token;

  @override
  Future<void> saveToken(String token, DateTime expiresAt) async {
    this.token = token;
  }
}

final class _FakeRemoteShareApi implements RemoteShareApi {
  var sendCalls = 0;
  ShareResourceType? lastResourceType;
  List<String> lastRecipientEmails = const [];

  @override
  Future<RemoteSyncedItem> acceptShare(String shareId, String token) async {
    return RemoteSyncedItem(
      resourceType: SyncResourceType.program,
      clientId: 'program-remote',
      serverId: 'program-server',
      schemaVersion: 1,
      clientUpdatedAt: DateTime.utc(2026, 7, 17),
      serverUpdatedAt: DateTime.utc(2026, 7, 17),
      deletedAt: null,
      payload: const {'name': 'Programme partagé'},
    );
  }

  @override
  Future<void> declineShare(String shareId, String token) async {}

  @override
  Future<List<ShareInboxItem>> fetchInbox(String token) async => const [];

  @override
  Future<void> revokeShare(String shareId, String token) async {}

  @override
  Future<RemoteShareSendResult> sendShare({
    required ShareResourceType resourceType,
    required Map<String, Object?> payload,
    required List<String> recipientEmails,
    required String token,
  }) async {
    sendCalls += 1;
    lastResourceType = resourceType;
    lastRecipientEmails = recipientEmails;
    return const RemoteShareSendResult(
      shareId: 'share-1',
      recipientUserIds: ['recipient-1'],
      unresolvedEmails: [],
    );
  }
}

final class _FakeShareInboxRepository implements ShareInboxRepository {
  @override
  Future<ShareInboxItem?> findByShareId(String shareId) async => null;

  @override
  Future<List<ShareInboxItem>> listAll() async => const [];

  @override
  Future<void> markStatus(
    String shareId,
    ShareInboxStatus status,
    DateTime respondedAt,
  ) async {}

  @override
  Future<void> upsert(ShareInboxItem item) async {}

  @override
  Future<void> upsertAll(List<ShareInboxItem> items) async {}
}

final class _FakePendingShareActionRepository
    implements PendingShareActionRepository {
  @override
  Future<void> add(PendingShareAction action) async {}

  @override
  Future<List<PendingShareAction>> listPending() async => const [];

  @override
  Future<void> markFailed(String id, DateTime attemptedAt) async {}

  @override
  Future<void> markSucceeded(String id, DateTime attemptedAt) async {}
}

final class _FakeLocalSyncChangeRepository
    implements LocalSyncChangeRepository {
  @override
  Future<bool> applyRemoteItem(RemoteSyncedItem item) async => true;

  @override
  Future<List<PendingSyncChange>> listPendingChanges() async => const [];

  @override
  Future<void> markChangesSynced(
    List<String> changeLogIds,
    DateTime syncedAt,
  ) async {}
}
