import 'dart:convert';

import 'package:flutter/material.dart';

import '../application/application.dart';
import '../domain/domain.dart';
import 'theme.dart';
import 'workout_execution_screen.dart';

final class HistoryListScreen extends StatefulWidget {
  const HistoryListScreen({
    required this.historyUseCases,
    required this.workoutTemplateUseCases,
    required this.activeUseCases,
    required this.closeUseCase,
    this.mediaUseCases,
    this.stepUseCases,
    this.performanceReferenceUseCase,
    this.onOpenProgression,
    super.key,
  });

  final WorkoutHistoryUseCases historyUseCases;
  final WorkoutTemplateUseCases workoutTemplateUseCases;
  final ActiveWorkoutSessionUseCases activeUseCases;
  final ActiveExerciseStepUseCases? stepUseCases;
  final ExercisePerformanceReferenceUseCase? performanceReferenceUseCase;
  final CloseWorkoutSessionUseCase closeUseCase;
  final MediaUseCases? mediaUseCases;
  final VoidCallback? onOpenProgression;

  @override
  State<HistoryListScreen> createState() => _HistoryListScreenState();
}

final class _HistoryListScreenState extends State<HistoryListScreen> {
  late Future<List<WorkoutHistory>> _histories;

  @override
  void initState() {
    super.initState();
    _histories = widget.historyUseCases.listActive();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Historique'),
        actions: [
          if (widget.onOpenProgression != null)
            TextButton(
              onPressed: widget.onOpenProgression,
              child: const Text('Progression'),
            ),
        ],
      ),
      body: FutureBuilder<List<WorkoutHistory>>(
        future: _histories,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          final histories = snapshot.data ?? const <WorkoutHistory>[];
          if (histories.isEmpty) {
            return const _CenteredMessage(
              title: 'Aucune séance terminée',
              message: 'Les séances terminées apparaîtront ici.',
            );
          }
          final groups = groupHistoriesByPeriod(histories, DateTime.now());
          return ListView(
            children: [
              for (final group in groups.entries) ...[
                Padding(
                  padding: const EdgeInsets.fromLTRB(16, 20, 16, 8),
                  child: Text(
                    group.key,
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                ),
                for (final history in group.value)
                  CourtBlazerAccentPanel(
                    margin: const EdgeInsets.fromLTRB(12, 0, 12, 8),
                    child: ListTile(
                      title: Text(history.nameSnapshot),
                      subtitle: Text(
                        '${_formatDateTime(history.startedAt)} · '
                        '${_formatDurationMs(history.totalActiveMs)} · '
                        '${_historySummary(history)}',
                      ),
                      trailing: const Icon(Icons.chevron_right),
                      onTap: () => _openDetail(history),
                    ),
                  ),
              ],
            ],
          );
        },
      ),
    );
  }

  Future<void> _openDetail(WorkoutHistory history) async {
    final deleted = await Navigator.of(context).push<bool>(
      MaterialPageRoute(
        builder: (context) => HistoryDetailScreen(
          history: history,
          historyUseCases: widget.historyUseCases,
          workoutTemplateUseCases: widget.workoutTemplateUseCases,
          activeUseCases: widget.activeUseCases,
          stepUseCases: widget.stepUseCases,
          performanceReferenceUseCase: widget.performanceReferenceUseCase,
          closeUseCase: widget.closeUseCase,
          mediaUseCases: widget.mediaUseCases,
        ),
      ),
    );
    if (deleted == true) {
      setState(() {
        _histories = widget.historyUseCases.listActive();
      });
    }
  }
}

final class HistoryDetailScreen extends StatelessWidget {
  const HistoryDetailScreen({
    required this.history,
    required this.historyUseCases,
    required this.workoutTemplateUseCases,
    required this.activeUseCases,
    required this.closeUseCase,
    this.mediaUseCases,
    this.stepUseCases,
    this.performanceReferenceUseCase,
    super.key,
  });

  final WorkoutHistory history;
  final WorkoutHistoryUseCases historyUseCases;
  final WorkoutTemplateUseCases workoutTemplateUseCases;
  final ActiveWorkoutSessionUseCases activeUseCases;
  final ActiveExerciseStepUseCases? stepUseCases;
  final ExercisePerformanceReferenceUseCase? performanceReferenceUseCase;
  final CloseWorkoutSessionUseCase closeUseCase;
  final MediaUseCases? mediaUseCases;

  @override
  Widget build(BuildContext context) {
    final detail = HistoryDetailData.fromHistory(history);
    return Scaffold(
      appBar: AppBar(title: Text(history.nameSnapshot)),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text(_formatDateTime(history.startedAt)),
          const SizedBox(height: 4),
          Text('Durée : ${_formatDurationMs(history.totalActiveMs)}'),
          const SizedBox(height: 16),
          for (final program in detail.programs) ...[
            Text(program.name, style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 8),
            for (final exercise in program.exercises) ...[
              Text(
                exercise.name,
                style: Theme.of(context).textTheme.titleSmall,
              ),
              for (final set in exercise.sets)
                Padding(
                  padding: const EdgeInsets.only(bottom: 8),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      ListTile(
                        dense: true,
                        contentPadding: EdgeInsets.zero,
                        title: Text('Série ${set.setIndex + 1}'),
                        subtitle: Text(set.summary),
                      ),
                      if (set.stepPassages.isNotEmpty)
                        _HistoryStepResultsSection(passages: set.stepPassages),
                    ],
                  ),
                ),
            ],
            const SizedBox(height: 12),
          ],
          const SizedBox(height: 16),
          FilledButton(
            onPressed: () => _restart(context),
            child: const Text('Relancer cette séance'),
          ),
          const SizedBox(height: 8),
          OutlinedButton(
            onPressed: () => _confirmDelete(context),
            child: const Text("Supprimer de l'historique"),
          ),
        ],
      ),
    );
  }

  Future<void> _restart(BuildContext context) async {
    ActiveWorkoutSession session;
    final sourceId = history.sourceWorkoutTemplateId;
    if (sourceId != null &&
        await workoutTemplateUseCases.findById(sourceId) != null) {
      session = await activeUseCases.startFromTemplate(sourceId);
    } else {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text(
              "La séance originale n'existe plus. Une copie va être utilisée.",
            ),
          ),
        );
      }
      session = await activeUseCases.startFromHistory(history);
    }
    if (!context.mounted) return;
    await Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => WorkoutExecutionScreen(
          initialSession: session,
          activeUseCases: activeUseCases,
          stepUseCases: stepUseCases,
          closeUseCase: closeUseCase,
          historyUseCases: historyUseCases,
          workoutTemplateUseCases: workoutTemplateUseCases,
          performanceReferenceUseCase: performanceReferenceUseCase,
          mediaUseCases: mediaUseCases,
        ),
      ),
    );
  }

  Future<void> _confirmDelete(BuildContext context) async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text("Supprimer de l'historique ?"),
        content: const Text('Cette action est définitive.'),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(false),
            child: const Text('Annuler'),
          ),
          FilledButton(
            onPressed: () => Navigator.of(context).pop(true),
            child: const Text('Supprimer'),
          ),
        ],
      ),
    );
    if (confirmed != true) return;
    await historyUseCases.delete(history.metadata.id);
    if (!context.mounted) return;
    Navigator.of(context).pop(true);
  }
}

final class HistoryDetailData {
  const HistoryDetailData({required this.programs});

  factory HistoryDetailData.fromHistory(WorkoutHistory history) {
    final snapshot =
        jsonDecode(history.historySnapshotJson) as Map<String, dynamic>;
    final resolved =
        jsonDecode(snapshot['resolvedTemplateSnapshotJson'] as String)
            as Map<String, dynamic>;
    final rawResults = history.results.isNotEmpty
        ? history.results.map(_resultFromDomain).toList()
        : (snapshot['results'] as List<dynamic>? ?? const [])
              .cast<Map<String, dynamic>>();
    final rawStepResults = history.stepResults.isNotEmpty
        ? history.stepResults.map(_stepResultFromDomain).toList()
        : (snapshot['stepResults'] as List<dynamic>? ?? const [])
              .cast<Map<String, dynamic>>();
    final programs = <HistoryProgramDetail>[];
    final rawPrograms = resolved['programs'] as List<dynamic>? ?? const [];
    for (
      var programIndex = 0;
      programIndex < rawPrograms.length;
      programIndex++
    ) {
      final rawProgram = rawPrograms[programIndex] as Map<String, dynamic>;
      final programSnapshot =
          jsonDecode(rawProgram['programSnapshotJson'] as String)
              as Map<String, dynamic>;
      final exercises = <HistoryExerciseDetail>[];
      final rawExercises =
          programSnapshot['exercises'] as List<dynamic>? ?? const [];
      for (
        var exerciseIndex = 0;
        exerciseIndex < rawExercises.length;
        exerciseIndex++
      ) {
        final rawExercise = rawExercises[exerciseIndex] as Map<String, dynamic>;
        final exerciseResults = rawResults.where((result) {
          return result['programIndex'] == programIndex &&
              result['exerciseIndex'] == exerciseIndex;
        }).toList();
        final exerciseStepResults = rawStepResults.where((result) {
          return result['programIndex'] == programIndex &&
              result['exerciseIndex'] == exerciseIndex;
        }).toList();
        exercises.add(
          HistoryExerciseDetail(
            name: rawExercise['exerciseNameSnapshot'] as String,
            sets: [
              for (final result in exerciseResults)
                HistorySetDetail.fromSnapshot(
                  rawExercise,
                  result,
                  exerciseStepResults
                      .where(
                        (stepResult) =>
                            stepResult['setIndex'] == result['setIndex'],
                      )
                      .toList(),
                ),
            ],
          ),
        );
      }
      programs.add(
        HistoryProgramDetail(
          name: rawProgram['programNameSnapshot'] as String,
          exercises: exercises,
        ),
      );
    }
    return HistoryDetailData(programs: programs);
  }

  final List<HistoryProgramDetail> programs;
}

final class HistoryProgramDetail {
  const HistoryProgramDetail({required this.name, required this.exercises});

  final String name;
  final List<HistoryExerciseDetail> exercises;
}

final class HistoryExerciseDetail {
  const HistoryExerciseDetail({required this.name, required this.sets});

  final String name;
  final List<HistorySetDetail> sets;
}

final class HistorySetDetail {
  const HistorySetDetail({
    required this.setIndex,
    required this.summary,
    required this.stepPassages,
  });

  factory HistorySetDetail.fromSnapshot(
    Map<String, dynamic> exercise,
    Map<String, dynamic> result,
    List<Map<String, dynamic>> stepResults,
  ) {
    final parts = <String>[];
    if (exercise['timeEnabled'] == true && result['actualTimeMs'] != null) {
      parts.add('Temps ${_formatDurationMs(result['actualTimeMs'] as int)}');
    }
    if (exercise['repsEnabled'] == true && result['actualReps'] != null) {
      parts.add('${result['actualReps']} répétitions');
    }
    if (exercise['scoreEnabled'] == true && result['actualScore'] != null) {
      final unit = exercise['scoreUnitSnapshot'] as String?;
      final value = _formatScoreValue(result['actualScore'] as num);
      parts.add(unit == null ? 'Score $value' : 'Score $value $unit');
    }
    if (exercise['scoreEnabled'] == true &&
        _historyScoreInputMode(exercise, result) == ScoreInputMode.stopwatch &&
        result['actualScoreTimeMs'] != null) {
      final value = _formatScoreStopwatch(
        Duration(milliseconds: result['actualScoreTimeMs'] as int),
      );
      final target = _targetScoreTimeMsFromSnapshot(exercise, result);
      if (target == null) {
        parts.add('Temps réalisé : $value');
      } else {
        parts.add(
          'Temps réalisé : $value / objectif '
          '${_formatScoreStopwatchTarget(Duration(milliseconds: target))}',
        );
      }
    }
    return HistorySetDetail(
      setIndex: result['setIndex'] as int,
      summary: parts.isEmpty ? 'Aucune mesure saisie' : parts.join(' · '),
      stepPassages: _stepPassagesFromResults(stepResults),
    );
  }

  final int setIndex;
  final String summary;
  final List<HistoryStepPassageDetail> stepPassages;
}

final class HistoryStepPassageDetail {
  const HistoryStepPassageDetail({required this.index, required this.steps});

  final int index;
  final List<HistoryStepResultDetail> steps;
}

final class HistoryStepResultDetail {
  const HistoryStepResultDetail({
    required this.index,
    required this.name,
    required this.status,
    required this.valueLabel,
    this.scoreLabel,
  });

  factory HistoryStepResultDetail.fromResult(Map<String, dynamic> result) {
    final status = _setResultStatusFromSnapshot(result['status']);
    return HistoryStepResultDetail(
      index: result['stepIndex'] as int,
      name: result['stepNameSnapshot'] as String,
      status: status,
      valueLabel: status == SetResultStatus.skipped
          ? null
          : _historyStepValueLabel(result),
      scoreLabel: status == SetResultStatus.skipped
          ? null
          : _historyStepScoreLabel(result),
    );
  }

  final int index;
  final String name;
  final SetResultStatus status;
  final String? valueLabel;
  final String? scoreLabel;
}

final class _HistoryStepResultsSection extends StatelessWidget {
  const _HistoryStepResultsSection({required this.passages});

  final List<HistoryStepPassageDetail> passages;

  @override
  Widget build(BuildContext context) {
    final tokens = courtBlazerTokensOf(context);
    return Padding(
      padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Étapes réalisées',
            style: Theme.of(context).textTheme.labelLarge,
          ),
          const SizedBox(height: 8),
          for (final passage in passages) ...[
            Text(
              'Passage ${passage.index + 1}',
              style: Theme.of(context).textTheme.labelMedium,
            ),
            const SizedBox(height: 4),
            for (final step in passage.steps)
              Padding(
                padding: const EdgeInsets.only(bottom: 4),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Expanded(child: Text(_historyStepLine(step))),
                    if (step.status == SetResultStatus.skipped) ...[
                      const SizedBox(width: 8),
                      Chip(
                        visualDensity: VisualDensity.compact,
                        label: const Text('Passée'),
                        side: BorderSide(color: tokens.border),
                      ),
                    ],
                  ],
                ),
              ),
            const SizedBox(height: 8),
          ],
        ],
      ),
    );
  }
}

Map<String, List<WorkoutHistory>> groupHistoriesByPeriod(
  List<WorkoutHistory> histories,
  DateTime now,
) {
  final today = DateTime(now.year, now.month, now.day);
  final weekStart = today.subtract(Duration(days: today.weekday - 1));
  final groups = <String, List<WorkoutHistory>>{
    "Aujourd'hui": [],
    'Cette semaine': [],
    'Plus ancien': [],
  };
  for (final history in histories) {
    final startedDay = DateTime(
      history.startedAt.year,
      history.startedAt.month,
      history.startedAt.day,
    );
    if (startedDay == today) {
      groups["Aujourd'hui"]!.add(history);
    } else if (startedDay.isAfter(weekStart) || startedDay == weekStart) {
      groups['Cette semaine']!.add(history);
    } else {
      groups['Plus ancien']!.add(history);
    }
  }
  groups.removeWhere((key, value) => value.isEmpty);
  return groups;
}

Map<String, dynamic> _resultFromDomain(WorkoutHistorySetResult result) {
  return {
    'programIndex': result.programIndex,
    'exerciseIndex': result.exerciseIndex,
    'setIndex': result.setIndex,
    'actualTimeMs': result.actualTimeMs,
    'actualReps': result.actualReps,
    'actualScore': result.actualScore,
    'actualScoreTimeMs': result.actualScoreTimeMs,
    'scoreInputModeSnapshot': result.scoreInputModeSnapshot.name,
    'targetScoreTimeMsSnapshot': result.targetScoreTimeMsSnapshot,
  };
}

Map<String, dynamic> _stepResultFromDomain(WorkoutHistoryStepResult result) {
  return {
    'programIndex': result.programIndex,
    'exerciseIndex': result.exerciseIndex,
    'setIndex': result.setIndex,
    'passageIndex': result.passageIndex,
    'stepIndex': result.stepIndex,
    'stepNameSnapshot': result.stepNameSnapshot,
    'stepTypeSnapshot': result.stepTypeSnapshot.name,
    'targetValueSnapshot': result.targetValueSnapshot,
    'hasScoreSnapshot': result.hasScoreSnapshot,
    'scoreInputModeSnapshot': result.scoreInputModeSnapshot?.name,
    'scoreLabelSnapshot': result.scoreLabelSnapshot,
    'scoreUnitSnapshot': result.scoreUnitSnapshot,
    'actualTimeMs': result.actualTimeMs,
    'actualReps': result.actualReps,
    'actualScore': result.actualScore,
    'actualScoreTimeMs': result.actualScoreTimeMs,
    'status': result.status.name,
  };
}

List<HistoryStepPassageDetail> _stepPassagesFromResults(
  List<Map<String, dynamic>> results,
) {
  if (results.isEmpty) return const [];
  final byPassage = <int, List<Map<String, dynamic>>>{};
  for (final result in results) {
    final passageIndex = result['passageIndex'] as int;
    byPassage.putIfAbsent(passageIndex, () => []).add(result);
  }
  final passages = byPassage.entries.toList()
    ..sort((left, right) => left.key.compareTo(right.key));
  return passages
      .map((entry) {
        final steps = entry.value
          ..sort(
            (left, right) =>
                (left['stepIndex'] as int).compareTo(right['stepIndex'] as int),
          );
        return HistoryStepPassageDetail(
          index: entry.key,
          steps: steps.map(HistoryStepResultDetail.fromResult).toList(),
        );
      })
      .toList(growable: false);
}

String _historyStepLine(HistoryStepResultDetail step) {
  final parts = <String>['Étape ${step.index + 1}', step.name];
  if (step.valueLabel != null) {
    parts.add(step.valueLabel!);
  }
  if (step.scoreLabel != null) {
    parts.add(step.scoreLabel!);
  }
  return parts.join(' · ');
}

String? _historyStepValueLabel(Map<String, dynamic> result) {
  final type = result['stepTypeSnapshot'];
  if (type == ExerciseStepType.time.name && result['actualTimeMs'] != null) {
    return 'Temps ${_formatDurationMs(result['actualTimeMs'] as int)}';
  }
  if (type == ExerciseStepType.reps.name && result['actualReps'] != null) {
    return 'Répétitions ${result['actualReps']}';
  }
  return null;
}

String? _historyStepScoreLabel(Map<String, dynamic> result) {
  if (result['hasScoreSnapshot'] != true) return null;
  final mode = _scoreInputModeFromSnapshot(result['scoreInputModeSnapshot']);
  if (mode == ScoreInputMode.stopwatch && result['actualScoreTimeMs'] != null) {
    return 'Score chrono : ${_formatScoreStopwatch(Duration(milliseconds: result['actualScoreTimeMs'] as int))}';
  }
  if (mode == ScoreInputMode.manual && result['actualScore'] != null) {
    final scoreLabel = result['scoreLabelSnapshot'] as String? ?? 'Score';
    final score = _formatScoreValue(result['actualScore'] as num);
    final unit = result['scoreUnitSnapshot'] as String?;
    final suffix = unit == null || unit.trim().isEmpty ? '' : ' $unit';
    return '$scoreLabel : $score$suffix';
  }
  return null;
}

SetResultStatus _setResultStatusFromSnapshot(Object? value) {
  return switch (value) {
    'skipped' => SetResultStatus.skipped,
    _ => SetResultStatus.completed,
  };
}

String _historySummary(WorkoutHistory history) {
  final snapshot =
      jsonDecode(history.historySnapshotJson) as Map<String, dynamic>;
  final rawResults = history.results.isNotEmpty
      ? history.results.map(_resultFromDomain).toList()
      : (snapshot['results'] as List<dynamic>? ?? const [])
            .cast<Map<String, dynamic>>();
  final scoreCount = rawResults
      .where(
        (result) =>
            result['actualScore'] != null ||
            result['actualScoreTimeMs'] != null,
      )
      .length;
  return '${rawResults.length} séries · $scoreCount score${scoreCount > 1 ? 's' : ''}';
}

String _formatScoreValue(num value) {
  if (value is int || value == value.roundToDouble()) {
    return value.round().toString();
  }
  return value.toString();
}

String _formatDateTime(DateTime value) {
  final day = value.day.toString().padLeft(2, '0');
  final month = value.month.toString().padLeft(2, '0');
  final hour = value.hour.toString().padLeft(2, '0');
  final minute = value.minute.toString().padLeft(2, '0');
  return '$day/$month/${value.year} $hour:$minute';
}

String _formatDurationMs(int milliseconds) {
  final duration = Duration(milliseconds: milliseconds);
  final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
  final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
  final hours = duration.inHours;
  if (hours > 0) {
    return '$hours:$minutes:$seconds';
  }
  return '$minutes:$seconds';
}

String _formatScoreStopwatch(Duration duration) {
  final totalTenths = (duration.inMilliseconds / 100).floor();
  final minutes = (totalTenths ~/ 600).toString().padLeft(2, '0');
  final seconds = ((totalTenths ~/ 10) % 60).toString().padLeft(2, '0');
  final tenths = totalTenths % 10;
  return '$minutes:$seconds.$tenths';
}

String _formatScoreStopwatchTarget(Duration duration) {
  if (duration.inMilliseconds % 1000 == 0) {
    return _formatDurationMs(duration.inMilliseconds);
  }
  return _formatScoreStopwatch(duration);
}

ScoreInputMode _scoreInputModeFromSnapshot(Object? value) {
  return switch (value) {
    'stopwatch' => ScoreInputMode.stopwatch,
    _ => ScoreInputMode.manual,
  };
}

int? _targetScoreTimeMsFromSnapshot(
  Map<String, dynamic> exercise,
  Map<String, dynamic> result,
) {
  return result['targetScoreTimeMsSnapshot'] as int? ??
      (exercise['targetScoreTimeMs'] as int?);
}

ScoreInputMode _historyScoreInputMode(
  Map<String, dynamic> exercise,
  Map<String, dynamic> result,
) {
  final resultMode = result['scoreInputModeSnapshot'];
  if (resultMode != null) {
    return _scoreInputModeFromSnapshot(resultMode);
  }
  return _scoreInputModeFromSnapshot(exercise['scoreInputModeSnapshot']);
}

final class _CenteredMessage extends StatelessWidget {
  const _CenteredMessage({required this.title, this.message});

  final String title;
  final String? message;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              title,
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            if (message != null) ...[
              const SizedBox(height: 8),
              Text(message!, textAlign: TextAlign.center),
            ],
          ],
        ),
      ),
    );
  }
}
