import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:slrpg_app/src/features/gamification/application/quest_service.dart';
import 'package:slrpg_app/src/shared/data/remote/sync_service.dart';
import 'package:slrpg_app/src/shared/data/repositories/cycle_repository.dart';
import 'package:slrpg_app/src/shared/data/repositories/user_repository.dart';
import 'package:slrpg_app/src/shared/data/repositories/workout_repository.dart';
import 'package:slrpg_app/src/shared/domain/entities/exercise.dart';
import 'package:slrpg_app/src/shared/domain/logic/xp_calculator.dart';

part 'workout_completion_service.g.dart';

class WorkoutCompletionResult {
  final int xpEarned;
  final int? oldLevel;
  final int? newLevel;

  const WorkoutCompletionResult({
    required this.xpEarned,
    this.oldLevel,
    this.newLevel,
  });

  bool get hasLevelUp =>
      oldLevel != null && newLevel != null && newLevel! > oldLevel!;
}

@riverpod
WorkoutCompletionService workoutCompletionService(Ref ref) {
  return WorkoutCompletionService(
    userRepo: ref.watch(userRepositoryProvider),
    workoutRepo: ref.watch(workoutRepositoryProvider),
    cycleRepo: ref.watch(cycleRepositoryProvider),
    questService: ref.watch(questServiceProvider),
    syncService: ref.watch(syncServiceProvider),
  );
}

class WorkoutCompletionService {
  final UserRepository userRepo;
  final WorkoutRepository workoutRepo;
  final CycleRepository cycleRepo;
  final QuestService questService;
  final SyncService syncService;

  WorkoutCompletionService({
    required this.userRepo,
    required this.workoutRepo,
    required this.cycleRepo,
    required this.questService,
    required this.syncService,
  });

  Future<WorkoutCompletionResult> completeWorkout({
    required List<Exercise> exercises,
    required int week,
    required int day,
    int? workoutId,
  }) async {
    final xpEarned = XPCalculator.calculateWorkoutXP(exercises);

    await userRepo.updateXP(xpEarned);

    int? oldLevel;
    int? newLevel;
    final user = await userRepo.getLocalUser();
    if (user != null) {
      final calculatedLevel = XPCalculator.calculateLevelFromXP(user.xp);
      if (calculatedLevel > user.level) {
        oldLevel = user.level;
        newLevel = calculatedLevel;
        await userRepo.updateLevel(calculatedLevel);
      }
    }

    await _reportQuestEvents(exercises);

    await _saveWorkout(
      exercises: exercises,
      week: week,
      day: day,
      workoutId: workoutId,
      xpEarned: xpEarned,
    );

    syncService.sync();

    return WorkoutCompletionResult(
      xpEarned: xpEarned,
      oldLevel: oldLevel,
      newLevel: newLevel,
    );
  }

  Future<void> _reportQuestEvents(List<Exercise> exercises) async {
    await questService.reportEvent(QuestTrigger.workoutComplete);

    int totalVolume = 0;
    int totalReps = 0;

    for (var ex in exercises) {
      for (var set in ex.sets) {
        if (set.completed) {
          totalVolume += (set.targetWeightTotal * set.repsActual).round();
          totalReps += set.repsActual;
        }
      }
    }

    if (totalVolume > 0) {
      await questService.reportEvent(QuestTrigger.volume, data: totalVolume);
    }
    if (totalReps > 0) {
      await questService.reportEvent(QuestTrigger.repCount, data: totalReps);
    }
  }

  Future<void> _saveWorkout({
    required List<Exercise> exercises,
    required int week,
    required int day,
    int? workoutId,
    required int xpEarned,
  }) async {
    final cycle = await cycleRepo.getCurrentCycle();
    final cycleIdRef = cycle?.serverId ?? cycle?.id.toString() ?? '';

    if (workoutId != null) {
      var workout = await workoutRepo.getWorkoutByWeekDay(
        cycleId: cycleIdRef,
        week: week,
        day: day,
      );

      if (workout != null) {
        final updatedExercises = exercises.map((e) => e.toJson()).toList();
        final updatedWorkout = workout.copyWith(exercises: updatedExercises);
        await workoutRepo.completeWorkout(updatedWorkout, xpEarned: xpEarned);
      }
    } else {
      final exercisesJson = exercises.map((e) => e.toJson()).toList();
      await workoutRepo.createCompletedWorkout(
        cycleId: cycleIdRef,
        week: week,
        day: day,
        exercises: exercisesJson,
        xpEarned: xpEarned,
        durationSeconds: 0,
      );
    }
  }
}
