import 'dart:convert';

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 séances seedées', (
    tester,
  ) async {
    final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
    final templateRepository = _FakeWorkoutTemplateRepository([
      _workoutTemplate(isExample: true),
    ]);
    final activeRepository = _FakeActiveSessionRepository();
    final programRepository = _FakeProgramRepository();
    final historyRepository = _FakeWorkoutHistoryRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: WorkoutTemplateListScreen(
          workoutTemplateUseCases: WorkoutTemplateUseCases(
            templateRepository: templateRepository,
            programRepository: programRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          programUseCases: ProgramUseCases(
            programRepository: programRepository,
            exerciseRepository: _FakeExerciseRepository(),
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          activeUseCases: ActiveWorkoutSessionUseCases(
            sessionRepository: activeRepository,
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          closeUseCase: CloseWorkoutSessionUseCase(
            sessionRepository: activeRepository,
            historyRepository: historyRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          historyUseCases: WorkoutHistoryUseCases(
            repository: historyRepository,
            clock: clock,
          ),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

    expect(find.text('Séance jambes'), findsOneWidget);
    expect(find.text('Exemple'), findsOneWidget);
    expect(find.byTooltip('Actions'), findsOneWidget);
    expect(find.text('Lancer'), findsOneWidget);
  });

  testWidgets('la liste séances vide conserve le message dédié', (
    tester,
  ) async {
    final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
    final templateRepository = _FakeWorkoutTemplateRepository(const []);
    final activeRepository = _FakeActiveSessionRepository();
    final programRepository = _FakeProgramRepository();
    final historyRepository = _FakeWorkoutHistoryRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: WorkoutTemplateListScreen(
          workoutTemplateUseCases: WorkoutTemplateUseCases(
            templateRepository: templateRepository,
            programRepository: programRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          programUseCases: ProgramUseCases(
            programRepository: programRepository,
            exerciseRepository: _FakeExerciseRepository(),
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          activeUseCases: ActiveWorkoutSessionUseCases(
            sessionRepository: activeRepository,
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          closeUseCase: CloseWorkoutSessionUseCase(
            sessionRepository: activeRepository,
            historyRepository: historyRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          historyUseCases: WorkoutHistoryUseCases(
            repository: historyRepository,
            clock: clock,
          ),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

    expect(find.text('Aucune séance'), findsOneWidget);
    expect(
      find.text(
        'Crée une séance-modèle pour lancer ton entraînement plus vite.',
      ),
      findsOneWidget,
    );
  });

  testWidgets('filtre les séances par tag et peut effacer les filtres', (
    tester,
  ) async {
    final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
    final templateRepository = _FakeWorkoutTemplateRepository([
      _workoutTemplate(tags: const ['match', 'intense']),
      WorkoutTemplate(
        metadata: _metadata('template-2'),
        name: 'Séance routine',
        tags: const ['routine'],
      ),
    ]);
    final activeRepository = _FakeActiveSessionRepository();
    final programRepository = _FakeProgramRepository();
    final historyRepository = _FakeWorkoutHistoryRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: WorkoutTemplateListScreen(
          workoutTemplateUseCases: WorkoutTemplateUseCases(
            templateRepository: templateRepository,
            programRepository: programRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          programUseCases: ProgramUseCases(
            programRepository: programRepository,
            exerciseRepository: _FakeExerciseRepository(),
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          activeUseCases: ActiveWorkoutSessionUseCases(
            sessionRepository: activeRepository,
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          closeUseCase: CloseWorkoutSessionUseCase(
            sessionRepository: activeRepository,
            historyRepository: historyRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          historyUseCases: WorkoutHistoryUseCases(
            repository: historyRepository,
            clock: clock,
          ),
        ),
      ),
    );
    await tester.pump();
    await tester.pump();

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

    expect(find.text('Séance jambes'), findsOneWidget);
    expect(find.text('Séance routine'), findsNothing);
    expect(find.text('Effacer les filtres'), findsOneWidget);

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

    expect(find.text('Séance jambes'), findsOneWidget);
    expect(find.text('Séance routine'), findsOneWidget);
  });

  testWidgets('dupliquer une séance ouvre la copie en édition', (tester) async {
    final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
    final templateRepository = _FakeWorkoutTemplateRepository([
      _workoutTemplate(tags: const ['match']),
    ]);
    final activeRepository = _FakeActiveSessionRepository();
    final programRepository = _FakeProgramRepository();
    final historyRepository = _FakeWorkoutHistoryRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: WorkoutTemplateListScreen(
          workoutTemplateUseCases: WorkoutTemplateUseCases(
            templateRepository: templateRepository,
            programRepository: programRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          programUseCases: ProgramUseCases(
            programRepository: programRepository,
            exerciseRepository: _FakeExerciseRepository(),
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          activeUseCases: ActiveWorkoutSessionUseCases(
            sessionRepository: activeRepository,
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          closeUseCase: CloseWorkoutSessionUseCase(
            sessionRepository: activeRepository,
            historyRepository: historyRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          historyUseCases: WorkoutHistoryUseCases(
            repository: historyRepository,
            clock: clock,
          ),
        ),
      ),
    );
    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 la séance'), findsOneWidget);
    expect(find.text('Copie de Séance jambes'), findsOneWidget);
    expect(
      find.text('Séance dupliquée. Ajuste la copie avant de la lancer.'),
      findsOneWidget,
    );
    expect(find.widgetWithText(Chip, 'match'), findsOneWidget);
  });

  testWidgets('modifier une séance exemple conserve sa provenance', (
    tester,
  ) async {
    final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
    final templateRepository = _FakeWorkoutTemplateRepository([
      _workoutTemplate(isExample: true),
    ]);
    final programRepository = _FakeProgramRepository();

    await tester.pumpWidget(
      MaterialApp(
        home: WorkoutTemplateFormScreen(
          workoutTemplateUseCases: WorkoutTemplateUseCases(
            templateRepository: templateRepository,
            programRepository: programRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          programUseCases: ProgramUseCases(
            programRepository: programRepository,
            exerciseRepository: _FakeExerciseRepository(),
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          template: templateRepository.templates.single,
        ),
      ),
    );

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

    expect(templateRepository.templates.single.name, 'Editée');
    expect(templateRepository.templates.single.isExample, isTrue);
  });

  testWidgets(
    'la vue programme de séance ne permet pas de changer les mesures actives',
    (tester) async {
      final program = WorkoutTemplateProgramDraft(
        clientKey: 'program-1',
        programNameSnapshot: 'Jambes',
        defaultRestSecondsSnapshot: 60,
        programSnapshotJson: '{}',
        exercises: [
          WorkoutTemplateExerciseDraft(
            snapshotExerciseId: 'exercise-1',
            exerciseNameSnapshot: 'Squat',
            setsCount: 3,
            timeEnabled: true,
            repsEnabled: true,
            scoreEnabled: false,
            targetTimeSeconds: 45,
            targetReps: 10,
          ),
        ],
      );

      await tester.pumpWidget(
        MaterialApp(home: WorkoutTemplateProgramDetailScreen(program: program)),
      );

      expect(find.byType(CheckboxListTile), findsNothing);
      expect(find.text('Mesures à suivre'), findsNothing);

      await tester.enterText(find.byType(TextFormField).first, '5');
      await tester.pump();

      expect(program.exercises.single.setsCountOverride, 5);
      expect(find.text('5 séries · Temps + Répétitions'), findsOneWidget);
    },
  );

  testWidgets(
    'personnaliser l’enchaînement automatique transmet l’override séance',
    (tester) async {
      final program = WorkoutTemplateProgramDraft(
        clientKey: 'program-1',
        programNameSnapshot: 'Circuit',
        defaultRestSecondsSnapshot: 60,
        programSnapshotJson: '{}',
        exercises: [
          WorkoutTemplateExerciseDraft(
            snapshotExerciseId: 'exercise-1',
            exerciseNameSnapshot: 'Navettes',
            setsCount: 3,
            timeEnabled: true,
            repsEnabled: false,
            scoreEnabled: false,
            exerciseStepsSnapshot: [
              _step(
                id: 'step-1',
                position: 0,
                name: 'Course',
                type: ExerciseStepType.time,
              ),
              _step(
                id: 'step-2',
                position: 1,
                name: 'Retour',
                type: ExerciseStepType.time,
              ),
            ],
            autoStartNextTimedStepSnapshot: true,
            targetTimeSeconds: 30,
          ),
        ],
      );

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

      await tester.pumpWidget(
        MaterialApp(home: WorkoutTemplateProgramDetailScreen(program: program)),
      );

      await tester.tap(
        find.widgetWithText(
          SwitchListTile,
          'Enchaîner automatiquement les chronos consécutifs',
        ),
      );
      await tester.pump();

      expect(
        find.textContaining('Personnalisé pour cette séance'),
        findsOneWidget,
      );
      final override = program.toOverrides().single;
      expect(override.autoStartNextTimedStepOverride, isFalse);
    },
  );

  testWidgets(
    'revenir au réglage du programme remet l’override séance à null',
    (tester) async {
      final program = WorkoutTemplateProgramDraft(
        clientKey: 'program-1',
        programNameSnapshot: 'Circuit',
        defaultRestSecondsSnapshot: 60,
        programSnapshotJson: '{}',
        exercises: [
          WorkoutTemplateExerciseDraft(
            snapshotExerciseId: 'exercise-1',
            exerciseNameSnapshot: 'Navettes',
            setsCount: 3,
            timeEnabled: true,
            repsEnabled: false,
            scoreEnabled: false,
            exerciseStepsSnapshot: [
              _step(
                id: 'step-1',
                position: 0,
                name: 'Course',
                type: ExerciseStepType.time,
              ),
            ],
            autoStartNextTimedStepSnapshot: false,
            autoStartNextTimedStepOverride: true,
            targetTimeSeconds: 30,
          ),
        ],
      );

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

      await tester.pumpWidget(
        MaterialApp(home: WorkoutTemplateProgramDetailScreen(program: program)),
      );

      expect(
        find.textContaining('Personnalisé pour cette séance'),
        findsOneWidget,
      );

      await tester.tap(find.text('Revenir au réglage du programme'));
      await tester.pump();

      expect(find.textContaining('Réglage du programme'), findsOneWidget);
      expect(program.toOverrides(), isEmpty);
    },
  );

  testWidgets(
    'lancer une séance alors qu’une autre est en cours affiche un message',
    (tester) async {
      final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
      final templateRepository = _FakeWorkoutTemplateRepository([
        _workoutTemplate(),
      ]);
      final activeRepository = _FakeActiveSessionRepository()
        ..session = _activeSession();
      final programRepository = _FakeProgramRepository();
      final historyRepository = _FakeWorkoutHistoryRepository();

      await tester.pumpWidget(
        MaterialApp(
          home: WorkoutTemplateListScreen(
            workoutTemplateUseCases: WorkoutTemplateUseCases(
              templateRepository: templateRepository,
              programRepository: programRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            programUseCases: ProgramUseCases(
              programRepository: programRepository,
              exerciseRepository: _FakeExerciseRepository(),
              templateRepository: templateRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            activeUseCases: ActiveWorkoutSessionUseCases(
              sessionRepository: activeRepository,
              templateRepository: templateRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            closeUseCase: CloseWorkoutSessionUseCase(
              sessionRepository: activeRepository,
              historyRepository: historyRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            historyUseCases: WorkoutHistoryUseCases(
              repository: historyRepository,
              clock: clock,
            ),
          ),
        ),
      );
      await tester.pump();
      await tester.pump();

      await tester.tap(find.text('Lancer'));
      await tester.pump();
      await tester.pump();

      expect(
        find.textContaining('Une séance est déjà en cours.'),
        findsOneWidget,
      );
      expect(activeRepository.saveCount, 0);
    },
  );

  testWidgets(
    'supprimer une séance demande confirmation puis la retire de la liste',
    (tester) async {
      final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
      final templateRepository = _FakeWorkoutTemplateRepository([
        _workoutTemplate(),
      ]);
      final activeRepository = _FakeActiveSessionRepository();
      final programRepository = _FakeProgramRepository();
      final historyRepository = _FakeWorkoutHistoryRepository();

      await tester.pumpWidget(
        MaterialApp(
          home: WorkoutTemplateListScreen(
            workoutTemplateUseCases: WorkoutTemplateUseCases(
              templateRepository: templateRepository,
              programRepository: programRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            programUseCases: ProgramUseCases(
              programRepository: programRepository,
              exerciseRepository: _FakeExerciseRepository(),
              templateRepository: templateRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            activeUseCases: ActiveWorkoutSessionUseCases(
              sessionRepository: activeRepository,
              templateRepository: templateRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            closeUseCase: CloseWorkoutSessionUseCase(
              sessionRepository: activeRepository,
              historyRepository: historyRepository,
              clock: clock,
              ids: _FakeIds(),
              originDeviceId: 'device-1',
            ),
            historyUseCases: WorkoutHistoryUseCases(
              repository: historyRepository,
              clock: clock,
            ),
          ),
        ),
      );
      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 Séance jambes ?'), findsOneWidget);
      expect(find.text('Cette action est irréversible.'), findsOneWidget);

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

      expect(find.text('Séance jambes'), findsOneWidget);
      expect(templateRepository.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('Séance jambes'), findsNothing);
      expect(templateRepository.saved.single.metadata.deletedAt, isNotNull);
    },
  );

  testWidgets('partager une séance ouvre le formulaire de partage', (
    tester,
  ) async {
    final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
    final templateRepository = _FakeWorkoutTemplateRepository([
      _workoutTemplate(),
    ]);
    final programRepository = _FakeProgramRepository();
    final remoteShareApi = _FakeRemoteShareApi();

    await tester.pumpWidget(
      MaterialApp(
        home: WorkoutTemplateFormScreen(
          workoutTemplateUseCases: WorkoutTemplateUseCases(
            templateRepository: templateRepository,
            programRepository: programRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          programUseCases: ProgramUseCases(
            programRepository: programRepository,
            exerciseRepository: _FakeExerciseRepository(),
            templateRepository: templateRepository,
            clock: clock,
            ids: _FakeIds(),
            originDeviceId: 'device-1',
          ),
          shareUseCases: _shareUseCases(
            templateRepository: templateRepository,
            programRepository: programRepository,
            remoteShareApi: remoteShareApi,
          ),
          template: templateRepository.templates.single,
        ),
      ),
    );

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

    expect(find.text('Partager la séance'), findsOneWidget);

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

    expect(remoteShareApi.lastResourceType, ShareResourceType.workoutTemplate);
    expect(remoteShareApi.lastRecipientEmails, ['coach@example.com']);
  });
}

WorkoutTemplate _workoutTemplate({
  bool isExample = false,
  List<String> tags = const [],
}) {
  return WorkoutTemplate(
    metadata: _metadata('template-1'),
    name: 'Séance jambes',
    isExample: isExample,
    tags: tags,
    programs: [
      WorkoutTemplateProgram(
        metadata: _metadata('template-program-1'),
        workoutTemplateId: 'template-1',
        position: 0,
        programNameSnapshot: 'Programme jambes',
        defaultRestSecondsSnapshot: 60,
        programSnapshotJson: _programSnapshotJson(),
      ),
    ],
  );
}

ActiveWorkoutSession _activeSession() {
  return ActiveWorkoutSession(
    metadata: _metadata('session-1'),
    sourceWorkoutTemplateId: 'template-active',
    status: ActiveWorkoutStatus.running,
    startedAt: DateTime.utc(2026, 7, 17, 12),
    lastPersistedAt: DateTime.utc(2026, 7, 17, 12),
    elapsedActiveMs: 0,
    currentProgramIndex: 0,
    currentExerciseIndex: 0,
    currentSetIndex: 0,
    resolvedTemplateSnapshotJson: jsonEncode({
      'name': 'Séance active',
      'programs': [
        {
          'id': 'template-program-1',
          'programNameSnapshot': 'Programme jambes',
          'programSnapshotJson': _programSnapshotJson(),
        },
      ],
      'overrides': const [],
    }),
  );
}

String _programSnapshotJson() {
  return jsonEncode({
    'exercises': [
      {
        'id': 'exercise-1',
        'exerciseNameSnapshot': 'Squat',
        'setsCount': 3,
        'timeEnabled': true,
        'repsEnabled': false,
        'scoreEnabled': false,
        'restSecondsOverride': 60,
      },
    ],
  });
}

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 _FakeWorkoutTemplateRepository
    implements WorkoutTemplateRepository {
  _FakeWorkoutTemplateRepository(this.templates);

  final List<WorkoutTemplate> templates;
  final saved = <WorkoutTemplate>[];

  @override
  Future<WorkoutTemplate?> findById(String id) async {
    return templates.cast<WorkoutTemplate?>().firstWhere(
      (template) => template?.metadata.id == id,
      orElse: () => 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 {
    saved.add(template);
    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 {}
}

final class _FakeActiveSessionRepository implements ActiveSessionRepository {
  ActiveWorkoutSession? session;
  var saveCount = 0;

  @override
  Future<ActiveWorkoutSession?> findById(String id) async {
    return session?.metadata.id == id ? session : null;
  }

  @override
  Future<ActiveWorkoutSession?> findOpen() async => session;

  @override
  Future<ActiveRestState?> findRestStateById(String id) async => null;

  @override
  Future<ActiveScoreStopwatchState?> findScoreStopwatchState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
  }) async {
    return null;
  }

  @override
  Future<ActiveSetTimerState?> findSetTimerState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
  }) async {
    return null;
  }

  @override
  Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
  }) async {
    return null;
  }

  @override
  Future<List<ActiveRestState>> listRestStates(String sessionId) async {
    return const [];
  }

  @override
  Future<List<ActiveScoreStopwatchState>> listScoreStopwatchStates(
    String sessionId,
  ) async {
    return const [];
  }

  @override
  Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId) async {
    return const [];
  }

  @override
  Future<List<ActiveSetResult>> listSetResults(String sessionId) async {
    return const [];
  }

  @override
  Future<List<ActiveExerciseStepProgressState>> listExerciseStepProgressStates(
    String sessionId,
  ) async {
    return const [];
  }

  @override
  Future<List<ActiveExerciseStepResult>> listExerciseStepResults(
    String sessionId,
  ) async {
    return const [];
  }

  @override
  Future<void> save(ActiveWorkoutSession session) async {
    saveCount += 1;
    this.session = session;
  }

  @override
  Future<void> saveRestState(ActiveRestState restState) async {}

  @override
  Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state) async {}

  @override
  Future<void> saveSetTimerState(ActiveSetTimerState state) async {}

  @override
  Future<void> saveExerciseStepProgressState(
    ActiveExerciseStepProgressState state,
  ) async {}

  @override
  Future<void> saveExerciseStepResult(ActiveExerciseStepResult result) async {}

  @override
  Future<void> deleteScoreStopwatchState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
    required DateTime deletedAt,
  }) async {}

  @override
  Future<void> saveSetResult(ActiveSetResult result) async {}
}

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 {}
}

final class _FakeExerciseRepository implements ExerciseRepository {
  @override
  Future<Exercise?> findById(String id) async => null;

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

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

  @override
  Future<void> save(Exercise exercise) async {}
}

final class _FakeWorkoutHistoryRepository implements WorkoutHistoryRepository {
  @override
  Future<void> delete(String id, DateTime deletedAt) async {}

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

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

  @override
  Future<void> save(WorkoutHistory history) async {}

  @override
  Future<void> saveSetResult(WorkoutHistorySetResult result) async {}

  @override
  Future<void> saveStepResult(WorkoutHistoryStepResult result) async {}
}

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

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 {
  ShareResourceType? lastResourceType;
  List<String> lastRecipientEmails = const [];

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

  @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 {
    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 {}
}
