import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:drift/drift.dart';
import 'package:logger/logger.dart';
import 'package:slrpg_app/main.dart';

import '../local/app_database.dart';
import '../remote/api_client.dart';

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

class UserRepository {
  final AppDatabase db;
  final ApiClient apiClient;
  final _logger = Logger();

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

  Future<UserCollection?> getLocalUser() async {
    return await (db.select(db.users)..limit(1)).getSingleOrNull();
  }

  Future<void> saveLocalUser(UserCollection user) async {
    final companion = user.toCompanion(true).copyWith(
          updatedAt: Value(DateTime.now()),
        );
    await db.into(db.users).insertOnConflictUpdate(companion);
  }

  Future<void> updateXP(int xpToAdd) async {
    final user = await getLocalUser();
    if (user != null) {
      final newXp = user.xp + xpToAdd;
      final companion = UsersCompanion(
        xp: Value(newXp),
        isDirty: const Value(true),
        updatedAt: Value(DateTime.now()),
      );
      await (db.update(db.users)..where((u) => u.id.equals(user.id)))
          .write(companion);
    }
  }

  Future<void> updateLevel(int newLevel) async {
    final user = await getLocalUser();
    if (user != null) {
      final companion = UsersCompanion(
        level: Value(newLevel),
        isDirty: const Value(true),
        updatedAt: Value(DateTime.now()),
      );
      await (db.update(db.users)..where((u) => u.id.equals(user.id)))
          .write(companion);
    }
  }

  Future<void> updateBodyweight(double bodyweight) async {
    final user = await getLocalUser();
    if (user != null) {
      final companion = UsersCompanion(
        currentBodyweight: Value(bodyweight),
        isDirty: const Value(true),
        updatedAt: Value(DateTime.now()),
      );
      await (db.update(db.users)..where((u) => u.id.equals(user.id)))
          .write(companion);

      try {
        await apiClient.updateBodyweight(bodyweight);
      } catch (e) {
        _logger.w('Failed to update bodyweight online, will sync later: $e');
      }
    }
  }

  Future<void> updateInventory(Map<String, dynamic> inventory) async {
    final user = await getLocalUser();
    if (user != null) {
      final companion = UsersCompanion(
        inventorySettings: Value(inventory),
        isDirty: const Value(true),
        updatedAt: Value(DateTime.now()),
      );
      await (db.update(db.users)..where((u) => u.id.equals(user.id)))
          .write(companion);

      try {
        await apiClient.updateInventory(inventory);
      } catch (e) {
        _logger.w('Failed to update inventory online, will sync later: $e');
      }
    }
  }

  Future<Map<String, dynamic>> getInventorySettingsAsync() async {
    final user = await getLocalUser();
    if (user?.inventorySettings != null) {
      return user!.inventorySettings!;
    }
    return {
      'bar_weight': 20.0,
      'plates': [20, 20, 10, 10, 5, 5, 2.5, 2.5, 1.25, 1.25],
      'bands': [],
    };
  }

  Future<List<double>> getAvailablePlates() async {
    final inventory = await getInventorySettingsAsync();
    final plates = inventory['plates'] as List?;
    return plates?.map((e) => (e as num).toDouble()).toList() ?? [];
  }

  Future<double> getBarWeight() async {
    final inventory = await getInventorySettingsAsync();
    return (inventory['bar_weight'] as num?)?.toDouble() ?? 20.0;
  }

  Future<void> resetProgress() async {
    final user = await getLocalUser();
    if (user != null) {
      try {
        await apiClient.resetProgress();
      } catch (e) {
        _logger.e('Failed to reset progress on server: $e');
        throw Exception(
            "Server connection required to reset progress. Please try again when online.");
      }

      final companion = UsersCompanion(
        xp: const Value(0),
        level: const Value(1),
        isDirty: const Value(false),
        updatedAt: Value(DateTime.now()),
      );
      await (db.update(db.users)..where((u) => u.id.equals(user.id)))
          .write(companion);

      await db.delete(db.cycles).go();
      await db.delete(db.workouts).go();
    }
  }
}
