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('Progression affiche la période par défaut et l’état vide global', (
    tester,
  ) async {
    final repository = _FakeProgressionStatsRepository();

    await tester.pumpWidget(_app(repository: repository));
    await tester.pumpAndSettle();

    expect(find.text('Progression'), findsOneWidget);
    expect(find.text('4 semaines'), findsOneWidget);
    expect(find.text('3 mois'), findsOneWidget);
    expect(find.text('Tout'), findsOneWidget);
    expect(find.text('Aucune progression à afficher'), findsOneWidget);
    expect(
      find.text(
        'Termine une séance pour voir ton volume, ton rythme et tes résultats évoluer ici.',
      ),
      findsOneWidget,
    );
    expect(find.text('Voir les séances'), findsOneWidget);
    expect(repository.periods, [ProgressionPeriod.fourWeeks]);
  });

  testWidgets('Progression affiche Volume et Régularité', (tester) async {
    final repository = _FakeProgressionStatsRepository()
      ..overview = ProgressionGlobalStatsData(
        completedSessionCount: 8,
        totalActiveMs: 20400000,
        activeWeekStarts: [
          DateTime.utc(2026, 7, 6),
          DateTime.utc(2026, 7, 13),
          DateTime.utc(2026, 7, 20),
        ],
        hasAnyCompletedHistory: true,
        firstCompletedSessionAt: DateTime.utc(2026, 7, 1),
      );

    await tester.pumpWidget(_app(repository: repository));
    await tester.pumpAndSettle();

    expect(find.text('Volume'), findsOneWidget);
    expect(find.text('Séances'), findsOneWidget);
    expect(find.text('8'), findsOneWidget);
    expect(find.text('Temps total'), findsOneWidget);
    expect(find.text('5 h 40'), findsOneWidget);
    expect(find.text('Régularité'), findsOneWidget);
    expect(find.text('Rythme récent'), findsOneWidget);
    expect(find.text('3 / 4 semaines actives'), findsOneWidget);
    expect(
      find.text('Une semaine active contient au moins une séance terminée.'),
      findsOneWidget,
    );
  });

  testWidgets('Par exercice affiche un exercice archivé et un point unique', (
    tester,
  ) async {
    final repository = _FakeProgressionStatsRepository()
      ..overview = ProgressionGlobalStatsData(
        completedSessionCount: 1,
        totalActiveMs: 1800000,
        activeWeekStarts: [DateTime.utc(2026, 7, 13)],
        hasAnyCompletedHistory: true,
      )
      ..exerciseOptions = [
        ProgressionExerciseOptionData(
          exerciseKey: 'shoot',
          nameSnapshot: 'Shoot ligne',
          isArchived: true,
          lastPerformedAt: DateTime.utc(2026, 7, 17),
        ),
      ]
      ..measuresByExercise = const {
        'shoot': [
          ProgressionMeasureOptionData(
            measure: ProgressionMeasure.reps,
            label: 'Répétitions',
            lowerIsBetter: false,
          ),
        ],
      }
      ..seriesByExerciseMeasure = {
        'shoot:${ProgressionMeasure.reps.name}': ProgressionExerciseSeriesData(
          exerciseKey: 'shoot',
          exerciseNameSnapshot: 'Shoot ligne',
          measure: ProgressionMeasureOptionData(
            measure: ProgressionMeasure.reps,
            label: 'Répétitions',
            lowerIsBetter: false,
          ),
          points: [
            ProgressionPointData(
              workoutHistoryId: 'history-1',
              workoutNameSnapshot: 'Prépa match',
              startedAt: DateTime.utc(2026, 7, 17),
              value: 45,
              rawValue: 45,
            ),
          ],
          hasAnyAllTimeData: true,
          hasAnyCompletedExerciseResult: true,
        ),
      };

    await tester.pumpWidget(_app(repository: repository));
    await tester.pumpAndSettle();

    expect(find.text('Par exercice'), findsOneWidget);
    expect(find.text('Choisir un exercice'), findsOneWidget);
    expect(find.text('Shoot ligne'), findsOneWidget);
    expect(find.text('Exercice archivé'), findsOneWidget);
    expect(find.text('Encore un point de départ'), findsOneWidget);
    expect(
      find.text(
        'Termine cet exercice dans une autre séance pour voir son évolution.',
      ),
      findsOneWidget,
    );
    expect(find.text('Dernière séance : 45 répétitions'), findsOneWidget);
  });

  testWidgets('Par exercice affiche Temps réalisé et Plus bas = mieux', (
    tester,
  ) async {
    final repository = _progressionRepositoryWithSeries(
      measure: const ProgressionMeasureOptionData(
        measure: ProgressionMeasure.stopwatchScore,
        label: 'Temps réalisé',
        lowerIsBetter: true,
      ),
      rawValues: [45100, 42800],
      values: [45100, 42800],
    );

    await tester.pumpWidget(_app(repository: repository));
    await tester.pumpAndSettle();

    expect(find.text('Temps réalisé'), findsOneWidget);
    expect(find.text('Plus bas = mieux'), findsOneWidget);
    expect(find.text('Meilleur : 00:42.8'), findsOneWidget);
    expect(find.text('Dernier : 00:42.8'), findsOneWidget);
  });

  testWidgets('tap sur un point affiche le détail et ouvre la séance', (
    tester,
  ) async {
    final historyRepository = _FakeWorkoutHistoryRepository()
      ..histories['history-2'] = _history(id: 'history-2');
    final repository = _progressionRepositoryWithSeries(
      measure: const ProgressionMeasureOptionData(
        measure: ProgressionMeasure.manualScore,
        label: 'Score',
        scoreLabel: 'Réussites',
        scoreUnit: 'paniers',
        lowerIsBetter: false,
      ),
      rawValues: [12.0, 14.0],
      values: [12, 14],
    );

    await tester.pumpWidget(
      _app(repository: repository, historyRepository: historyRepository),
    );
    await tester.pumpAndSettle();

    final pointFinder = find.byKey(const ValueKey('progression-point-1'));
    await tester.ensureVisible(pointFinder);
    await tester.pumpAndSettle();
    await tester.tap(pointFinder);
    await tester.pumpAndSettle();

    expect(find.text('12 juillet'), findsOneWidget);
    expect(find.text('Séance : Prépa match'), findsOneWidget);
    expect(find.text('Meilleur score : 14 paniers'), findsOneWidget);

    await tester.tap(find.text('Voir la séance'));
    await tester.pumpAndSettle();

    expect(historyRepository.requestedIds, ['history-2']);
    expect(find.text('Séance jambes'), findsWidgets);
  });
}

Widget _app({
  required _FakeProgressionStatsRepository repository,
  _FakeWorkoutHistoryRepository? historyRepository,
}) {
  final clock = _FakeClock(DateTime.utc(2026, 7, 22, 12));
  final activeRepository = _FakeActiveSessionRepository();
  final resolvedHistoryRepository =
      historyRepository ?? _FakeWorkoutHistoryRepository();
  return MaterialApp(
    theme: courtBlazerLightTheme(),
    home: ProgressionScreen(
      progressionStatsUseCase: ProgressionStatsUseCase(
        repository: repository,
        clock: clock,
      ),
      historyUseCases: WorkoutHistoryUseCases(
        repository: resolvedHistoryRepository,
        clock: clock,
      ),
      workoutTemplateUseCases: WorkoutTemplateUseCases(
        templateRepository: _FakeWorkoutTemplateRepository(),
        programRepository: _FakeProgramRepository(),
        clock: clock,
        ids: _FakeIds(),
        originDeviceId: 'device-1',
      ),
      activeUseCases: ActiveWorkoutSessionUseCases(
        sessionRepository: activeRepository,
        templateRepository: _FakeWorkoutTemplateRepository(),
        clock: clock,
        ids: _FakeIds(),
        originDeviceId: 'device-1',
      ),
      closeUseCase: CloseWorkoutSessionUseCase(
        sessionRepository: activeRepository,
        historyRepository: resolvedHistoryRepository,
        clock: clock,
        ids: _FakeIds(),
        originDeviceId: 'device-1',
      ),
    ),
  );
}

_FakeProgressionStatsRepository _progressionRepositoryWithSeries({
  required ProgressionMeasureOptionData measure,
  required List<Object> rawValues,
  required List<double> values,
}) {
  return _FakeProgressionStatsRepository()
    ..overview = ProgressionGlobalStatsData(
      completedSessionCount: 2,
      totalActiveMs: 3600000,
      activeWeekStarts: [DateTime.utc(2026, 7, 6), DateTime.utc(2026, 7, 13)],
      hasAnyCompletedHistory: true,
    )
    ..exerciseOptions = [
      ProgressionExerciseOptionData(
        exerciseKey: 'shoot',
        nameSnapshot: 'Shoot ligne',
        isArchived: false,
        lastPerformedAt: DateTime.utc(2026, 7, 17),
      ),
    ]
    ..measuresByExercise = {
      'shoot': [measure],
    }
    ..seriesByExerciseMeasure = {
      'shoot:${measure.measure.name}': ProgressionExerciseSeriesData(
        exerciseKey: 'shoot',
        exerciseNameSnapshot: 'Shoot ligne',
        measure: measure,
        points: [
          ProgressionPointData(
            workoutHistoryId: 'history-1',
            workoutNameSnapshot: 'Prépa match',
            startedAt: DateTime.utc(2026, 7, 5),
            value: values[0],
            rawValue: rawValues[0],
          ),
          ProgressionPointData(
            workoutHistoryId: 'history-2',
            workoutNameSnapshot: 'Prépa match',
            startedAt: DateTime.utc(2026, 7, 12),
            value: values[1],
            rawValue: rawValues[1],
          ),
        ],
        hasAnyAllTimeData: true,
        hasAnyCompletedExerciseResult: true,
      ),
    };
}

final class _FakeProgressionStatsRepository
    implements ProgressionStatsRepository {
  ProgressionGlobalStatsData overview = const ProgressionGlobalStatsData(
    completedSessionCount: 0,
    totalActiveMs: 0,
    activeWeekStarts: [],
    hasAnyCompletedHistory: false,
  );
  List<ProgressionExerciseOptionData> exerciseOptions = const [];
  Map<String, List<ProgressionMeasureOptionData>> measuresByExercise = const {};
  Map<String, ProgressionExerciseSeriesData> seriesByExerciseMeasure = const {};
  final periods = <ProgressionPeriod>[];

  @override
  Future<ProgressionGlobalStatsData> readGlobalStats(
    ProgressionDateRange range,
  ) async {
    periods.add(
      range.startedAt == null
          ? ProgressionPeriod.all
          : range.startedAt!.isAfter(DateTime.utc(2026, 5))
          ? ProgressionPeriod.fourWeeks
          : ProgressionPeriod.threeMonths,
    );
    return overview;
  }

  @override
  Future<List<ProgressionExerciseOptionData>> listExerciseOptions(
    ProgressionDateRange range,
  ) async {
    return exerciseOptions;
  }

  @override
  Future<List<ProgressionMeasureOptionData>> listMeasureOptions({
    required ProgressionDateRange range,
    required String exerciseKey,
  }) async {
    return measuresByExercise[exerciseKey] ?? const [];
  }

  @override
  Future<ProgressionExerciseSeriesData> readExerciseSeries({
    required ProgressionDateRange range,
    required String exerciseKey,
    required ProgressionMeasure measure,
  }) async {
    return seriesByExerciseMeasure['$exerciseKey:${measure.name}'] ??
        ProgressionExerciseSeriesData(
          exerciseKey: exerciseKey,
          exerciseNameSnapshot: exerciseKey,
          measure: ProgressionMeasureOptionData(
            measure: measure,
            label: 'Score',
            lowerIsBetter: false,
          ),
          points: const [],
          hasAnyAllTimeData: false,
          hasAnyCompletedExerciseResult: false,
        );
  }
}

WorkoutHistory _history({required String id}) {
  final start = DateTime.utc(2026, 7, 12, 10);
  return WorkoutHistory(
    metadata: _metadata(id),
    sourceWorkoutTemplateId: 'template-1',
    sourceActiveWorkoutSessionId: 'session-1',
    nameSnapshot: 'Séance jambes',
    startedAt: start,
    endedAt: start.add(const Duration(minutes: 30)),
    totalActiveMs: 1800000,
    completed: true,
    historySnapshotJson: jsonEncode({
      'sessionId': 'session-1',
      'resolvedTemplateSnapshotJson': jsonEncode({
        'name': 'Séance jambes',
        'programs': [
          {
            'id': 'program-snapshot-1',
            'programNameSnapshot': 'Programme jambes',
            'programSnapshotJson': jsonEncode({
              'exercises': [
                {
                  'id': 'exercise-snapshot-1',
                  'exerciseNameSnapshot': 'Squat',
                  'setsCount': 1,
                  'timeEnabled': true,
                  'repsEnabled': true,
                  'scoreEnabled': true,
                  'targetTimeSeconds': 45,
                  'targetReps': 10,
                  'targetScore': 80,
                  'scoreInputModeSnapshot': ScoreInputMode.manual.name,
                  'scoreLabelSnapshot': 'Charge',
                  'scoreUnitSnapshot': 'kg',
                  'restSecondsOverride': 0,
                },
              ],
            }),
          },
        ],
        'overrides': const [],
      }),
      'results': [
        {
          'programSnapshotId': 'program-snapshot-1',
          'exerciseSnapshotId': 'exercise-snapshot-1',
          'programIndex': 0,
          'exerciseIndex': 0,
          'setIndex': 0,
          'actualTimeMs': 45000,
          'actualReps': 10,
          'actualScore': 80,
          'scoreLabelSnapshot': 'Charge',
          'scoreInputModeSnapshot': ScoreInputMode.manual.name,
          'scoreUnitSnapshot': 'kg',
        },
      ],
    }),
  );
}

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 {
  _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 _FakeWorkoutHistoryRepository implements WorkoutHistoryRepository {
  final histories = <String, WorkoutHistory>{};
  final requestedIds = <String>[];

  @override
  Future<void> delete(String id, DateTime deletedAt) async {}

  @override
  Future<WorkoutHistory?> findById(String id) async {
    requestedIds.add(id);
    return histories[id];
  }

  @override
  Future<List<WorkoutHistory>> listActive() async => histories.values.toList();

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

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

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

final class _FakeWorkoutTemplateRepository
    implements WorkoutTemplateRepository {
  @override
  Future<WorkoutTemplate?> findById(String id) async => null;

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

  @override
  Future<void> replaceComposition(
    WorkoutTemplate template,
    DateTime deletedAt,
  ) async {}

  @override
  Future<void> save(WorkoutTemplate template) async {}

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

  @override
  Future<void> saveProgram(WorkoutTemplateProgram program) 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 _FakeActiveSessionRepository implements ActiveSessionRepository {
  @override
  Future<void> deleteScoreStopwatchState({
    required String sessionId,
    required int programIndex,
    required int exerciseIndex,
    required int setIndex,
    required DateTime deletedAt,
  }) async {}

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

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

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

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

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

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

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

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

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

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

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

  @override
  Future<void> save(ActiveWorkoutSession session) async {}

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

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

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

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

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

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