import '../models/group_balance.dart';
import '../models/expense.dart';
import '../services/error_service.dart';
import 'expense_repository.dart';
import '../models/user_balance.dart';
import '../models/settlement.dart';

class BalanceRepository {
  final ExpenseRepository _expenseRepository;
  final ErrorService _errorService;

  BalanceRepository({
    required ExpenseRepository expenseRepository,
    ErrorService? errorService,
  }) : _expenseRepository = expenseRepository,
       _errorService = errorService ?? ErrorService();

  // Calculer la balance d'un groupe
  Future<GroupBalance> calculateGroupBalance(String groupId) async {
    try {
      // Pour l'instant, on utilise une liste statique des dépenses
      // En production, vous récupéreriez depuis le stream
      final expenses = await _expenseRepository
          .getExpensesStream(groupId)
          .first;

      final userBalances = _calculateUserBalances(expenses);
      final settlements = _calculateOptimalSettlements(userBalances);
      final totalExpenses = expenses
          .where((e) => !e.isArchived)
          .fold(0.0, (sum, e) => sum + e.amountInEur);

      return GroupBalance(
        groupId: groupId,
        userBalances: userBalances,
        settlements: settlements,
        totalExpenses: totalExpenses,
        calculatedAt: DateTime.now(),
      );
    } catch (e) {
      _errorService.logError('BalanceRepository', 'Erreur calcul balance: $e');
      rethrow;
    }
  }

  // Calculer les balances individuelles
  List<UserBalance> _calculateUserBalances(List<Expense> expenses) {
    final Map<String, Map<String, dynamic>> userBalanceMap = {};

    // Initialiser les utilisateurs
    for (final expense in expenses) {
      if (expense.isArchived) continue;

      // Ajouter le payeur
      userBalanceMap.putIfAbsent(expense.paidById, () => {
        'name': expense.paidByName,
        'paid': 0.0,
        'owed': 0.0,
      });

      // Ajouter les participants
      for (final split in expense.splits) {
        userBalanceMap.putIfAbsent(split.userId, () => {
          'name': split.userName,
          'paid': 0.0,
          'owed': 0.0,
        });
      }
    }

    // Calculer les montants
    for (final expense in expenses) {
      if (expense.isArchived) continue;

      // Ajouter au montant payé
      userBalanceMap[expense.paidById]!['paid'] += expense.amountInEur;

      // Ajouter aux montants dus
      for (final split in expense.splits) {
        userBalanceMap[split.userId]!['owed'] += split.amount;
      }
    }

    // Convertir en liste de UserBalance
    return userBalanceMap.entries.map((entry) {
      final userId = entry.key;
      final data = entry.value;
      final paid = data['paid'] as double;
      final owed = data['owed'] as double;
      
      return UserBalance(
        userId: userId,
        userName: data['name'] as String,
        totalPaid: paid,
        totalOwed: owed,
        balance: paid - owed,
      );
    }).toList();
  }

  // Algorithme d'optimisation des règlements
  List<Settlement> _calculateOptimalSettlements(List<UserBalance> balances) {
    final settlements = <Settlement>[];
    
    // Séparer les créditeurs et débiteurs
    final creditors = balances.where((b) => b.shouldReceive).toList();
    final debtors = balances.where((b) => b.shouldPay).toList();

    // Trier par montant (plus gros montants en premier)
    creditors.sort((a, b) => b.balance.compareTo(a.balance));
    debtors.sort((a, b) => a.balance.compareTo(b.balance));

    // Créer des copies mutables des montants
    final creditorsRemaining = Map.fromEntries(
      creditors.map((c) => MapEntry(c.userId, c.balance))
    );
    final debtorsRemaining = Map.fromEntries(
      debtors.map((d) => MapEntry(d.userId, -d.balance))
    );

    // Algorithme glouton pour minimiser le nombre de transactions
    for (final creditor in creditors) {
      for (final debtor in debtors) {
        final creditAmount = creditorsRemaining[creditor.userId] ?? 0;
        final debtAmount = debtorsRemaining[debtor.userId] ?? 0;

        if (creditAmount <= 0.01 || debtAmount <= 0.01) continue;

        final settlementAmount = [creditAmount, debtAmount].reduce((a, b) => a < b ? a : b);

        settlements.add(Settlement(
          fromUserId: debtor.userId,
          fromUserName: debtor.userName,
          toUserId: creditor.userId,
          toUserName: creditor.userName,
          amount: settlementAmount,
        ));

        creditorsRemaining[creditor.userId] = creditAmount - settlementAmount;
        debtorsRemaining[debtor.userId] = debtAmount - settlementAmount;
      }
    }

    return settlements;
  }
}