import 'dart:developer';

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:drift/drift.dart';

import '../local/app_database.dart';
import '../remote/api_client.dart';
import '../../../../main.dart';
import 'user_repository.dart';
import '../../../core/constants/app_constants.dart';

final cycleRepositoryProvider = Provider<CycleRepository>((ref) {
  final db = ref.watch(appDatabaseProvider);
  final apiClient = ref.watch(apiClientProvider);
  return CycleRepository(db: db, apiClient: apiClient);
});

class CycleRepository {
  final AppDatabase db;
  final ApiClient apiClient;

  CycleRepository({required this.db, required this.apiClient});

  Future<CycleCollection?> getCurrentCycle() async {
    return await (db.select(db.cycles)
          ..where((c) => c.isActive.equals(true))
          ..orderBy([
            (t) =>
                OrderingTerm(expression: t.cycleNumber, mode: OrderingMode.desc)
          ])
          ..limit(1))
        .getSingleOrNull();
  }

  Future<List<CycleCollection>> getAllCycles() async {
    return await db.select(db.cycles).get();
  }

  Future<CycleCollection> createCycle(Map<String, double> trainingMaxes) async {
    return await db.transaction(() async {
      final currentCycle = await getCurrentCycle();
      if (currentCycle != null) {
        final updateOld = CyclesCompanion(
          isActive: const Value(false),
          endDate: Value(DateTime.now()),
          updatedAt: Value(DateTime.now()),
          isDirty: const Value(true),
        );
        await (db.update(db.cycles)..where((c) => c.id.equals(currentCycle.id)))
            .write(updateOld);
      }

      final allCycles = await getAllCycles();
      final nextNumber = allCycles.isEmpty
          ? 1
          : allCycles
                  .map((c) => c.cycleNumber)
                  .reduce((a, b) => a > b ? a : b) +
              1;

      final user = await (db.select(db.users)..limit(1)).getSingleOrNull();
      if (user == null) {
        throw Exception('No user found for cycle creation');
      }

      final newCycleCompanion = CyclesCompanion(
        userId: Value(user.serverId ?? user.id.toString()),
        cycleNumber: Value(nextNumber),
        startDate: Value(DateTime.now()),
        isActive: const Value(true),
        trainingMaxes: Value(trainingMaxes),
        isDirty: const Value(true),
        createdAt: Value(DateTime.now()),
        updatedAt: Value(DateTime.now()),
      );

      final newId = await db.into(db.cycles).insert(newCycleCompanion);
      var newCycle = await (db.select(db.cycles)
            ..where((c) => c.id.equals(newId)))
          .getSingle();

      try {
        final response = await apiClient.createCycle(trainingMaxes);
        await (db.update(db.cycles)..where((c) => c.id.equals(newId))).write(
          CyclesCompanion(
            serverId: Value(response['id']),
            isDirty: const Value(false),
          ),
        );
        newCycle = await (db.select(db.cycles)
              ..where((c) => c.id.equals(newId)))
            .getSingle();
      } catch (e) {
        // API Fehler ignorieren, wird später gesynct
      }

      return newCycle;
    });
  }

  Future<CycleCollection> finishCycle() async {
    final currentCycle = await getCurrentCycle();
    if (currentCycle == null) {
      throw Exception('No active cycle to finish');
    }

    final cycleIdRef = currentCycle.serverId ?? currentCycle.id.toString();
    final localCycleId = currentCycle.id.toString();

    final workoutsQuery = db.select(db.workouts)
      ..where((w) {
        final weekCheck = w.week.isSmallerThanValue(4);
        final completedCheck = w.completedAt.isNotNull();
        final cycleCheck =
            w.cycleId.equals(cycleIdRef) | w.cycleId.equals(localCycleId);
        return weekCheck & completedCheck & cycleCheck;
      });

    final completedMainWorkouts = (await workoutsQuery.get()).length;

    if (completedMainWorkouts < 9) {
      final missing = 9 - completedMainWorkouts;
      throw Exception(
          'Cycle incomplete! You still have $missing workouts left in the main phase (Weeks 1-3). Finish them before leveling up.');
    }

    final currentTMs = currentCycle.trainingMaxes;

    final newTMs = <String, double>{
      'squat': (currentTMs['squat'] as num?)?.toDouble() ?? 0.0,
      'pullup': (currentTMs['pullup'] as num?)?.toDouble() ?? 0.0,
      'dip': (currentTMs['dip'] as num?)?.toDouble() ?? 0.0,
    };

    final week3Workouts = await (db.select(db.workouts)
          ..where((w) {
            final weekCheck = w.week.equals(3);
            final cycleCheck =
                w.cycleId.equals(cycleIdRef) | w.cycleId.equals(localCycleId);
            return weekCheck & cycleCheck;
          }))
        .get();

    bool checkSuccess(String exerciseId) {
      for (var workout in week3Workouts) {
        try {
          final exercises = workout.exercises;

          for (var exData in exercises) {
            final ex = exData as Map<String, dynamic>;

            if (ex['exerciseId'] == exerciseId) {
              final sets = ex['sets'] as List;
              for (var sData in sets) {
                final s = sData as Map<String, dynamic>;
                if (s['isAmrap'] == true) {
                  final reps = s['repsActual'] as int? ?? 0;
                  if (reps >= 1) {
                    return true;
                  }
                }
              }
            }
          }
        } catch (e) {
          log('⚠️ Error checking lift success for $exerciseId: $e');
        }
      }
      return false;
    }

    if (checkSuccess('squat')) {
      newTMs['squat'] = newTMs['squat']! + AppConstants.lowerBodyIncrement;
      log('✅ Squat Progress: TM increased');
    } else {
      log('⚠️ Squat Stall: TM kept same');
    }

    if (checkSuccess('pullup')) {
      newTMs['pullup'] = newTMs['pullup']! + AppConstants.upperBodyIncrement;
      log('✅ Pullup Progress: TM increased');
    } else {
      log('⚠️ Pullup Stall: TM kept same');
    }

    if (checkSuccess('dip')) {
      newTMs['dip'] = newTMs['dip']! + AppConstants.upperBodyIncrement;
      log('✅ Dip Progress: TM increased');
    } else {
      log('⚠️ Dip Stall: TM kept same');
    }

    if (currentCycle.serverId != null) {
      try {
        await apiClient.finishCycle(currentCycle.serverId!);
      } catch (e) {
        // Fehler ignorieren
      }
    }

    return await createCycle(newTMs);
  }

  Future<Map<String, double>> getCurrentTrainingMaxesAsync() async {
    final cycle = await getCurrentCycle();
    if (cycle != null) {
      final tms = cycle.trainingMaxes;
      return {
        'squat': (tms['squat'] as num?)?.toDouble() ?? 0.0,
        'pullup': (tms['pullup'] as num?)?.toDouble() ?? 0.0,
        'dip': (tms['dip'] as num?)?.toDouble() ?? 0.0,
      };
    }
    return {'squat': 0.0, 'pullup': 0.0, 'dip': 0.0};
  }
}
