import '../models/group_balance.dart';
import '../models/expense.dart';
import '../models/group_statistics.dart';
import '../models/user_balance.dart';
import '../models/settlement.dart';
import '../repositories/balance_repository.dart';
import '../repositories/expense_repository.dart';
import 'error_service.dart';

class BalanceService {
  final BalanceRepository _balanceRepository;
  final ExpenseRepository _expenseRepository;
  final ErrorService _errorService;

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

  /// Calculer la balance complète d'un groupe
  Future<GroupBalance> calculateGroupBalance(String groupId) async {
    try {
      return await _balanceRepository.calculateGroupBalance(groupId);
    } catch (e) {
      _errorService.logError('BalanceService', 'Erreur calcul balance groupe: $e');
      rethrow;
    }
  }

  /// Stream de la balance en temps réel
  Stream<GroupBalance> getGroupBalanceStream(String groupId) {
    return _expenseRepository.getExpensesStream(groupId).asyncMap((expenses) async {
      try {
        final userBalances = calculateUserBalances(expenses);
        final settlements = optimizeSettlements(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('BalanceService', 'Erreur stream balance: $e');
        rethrow;
      }
    });
  }

  /// Calculer les balances individuelles (logique métier)
  List<UserBalance> calculateUserBalances(List<Expense> expenses) {
    final Map<String, _UserBalanceCalculator> calculators = {};

    // Initialiser les calculateurs pour chaque utilisateur
    for (final expense in expenses) {
      if (expense.isArchived) continue;

      // Ajouter le payeur
      calculators.putIfAbsent(
        expense.paidById,
        () => _UserBalanceCalculator(expense.paidById, expense.paidByName),
      );

      // Ajouter les participants
      for (final split in expense.splits) {
        calculators.putIfAbsent(
          split.userId,
          () => _UserBalanceCalculator(split.userId, split.userName),
        );
      }
    }

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

      // Ajouter au montant payé
      calculators[expense.paidById]!.addPaid(expense.amountInEur);

      // Ajouter aux montants dus
      for (final split in expense.splits) {
        calculators[split.userId]!.addOwed(split.amount);
      }
    }

    // Convertir en UserBalance
    return calculators.values.map((calc) => calc.toUserBalance()).toList();
  }

  /// Optimiser les règlements (algorithme avancé)
  List<Settlement> optimizeSettlements(List<UserBalance> balances) {
    final settlements = <Settlement>[];
    
    // Filtrer les utilisateurs avec une balance significative (> 0.01€)
    final creditors = balances
        .where((b) => b.shouldReceive && b.balance > 0.01)
        .toList();
    final debtors = balances
        .where((b) => b.shouldPay && b.balance < -0.01)
        .toList();

    if (creditors.isEmpty || debtors.isEmpty) {
      return settlements;
    }

    // Trier par montant absolu décroissant pour optimiser
    creditors.sort((a, b) => b.balance.compareTo(a.balance));
    debtors.sort((a, b) => a.balance.compareTo(b.balance));

    // Utiliser des copies mutables pour les calculs
    final creditorsRemaining = Map<String, double>.fromEntries(
      creditors.map((c) => MapEntry(c.userId, c.balance))
    );
    final debtorsRemaining = Map<String, double>.fromEntries(
      debtors.map((d) => MapEntry(d.userId, -d.balance))
    );

    // Algorithme glouton optimisé
    for (final creditor in creditors) {
      final creditRemaining = creditorsRemaining[creditor.userId] ?? 0;
      if (creditRemaining <= 0.01) continue;

      for (final debtor in debtors) {
        final debtRemaining = debtorsRemaining[debtor.userId] ?? 0;
        if (debtRemaining <= 0.01) continue;

        final settlementAmount = _calculateOptimalSettlementAmount(
          creditRemaining,
          debtRemaining,
        );

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

          // Mettre à jour les montants restants
          creditorsRemaining[creditor.userId] = creditRemaining - settlementAmount;
          debtorsRemaining[debtor.userId] = debtRemaining - settlementAmount;
        }
      }
    }

    return _validateSettlements(settlements);
  }

  /// Calculer le montant optimal pour un règlement
  double _calculateOptimalSettlementAmount(double creditAmount, double debtAmount) {
    final amount = [creditAmount, debtAmount].reduce((a, b) => a < b ? a : b);
    // Arrondir à 2 décimales
    return (amount * 100).round() / 100;
  }

  /// Valider les règlements calculés
  List<Settlement> _validateSettlements(List<Settlement> settlements) {
    // Supprimer les règlements trop petits
    final validSettlements = settlements
        .where((s) => s.amount > 0.01)
        .toList();

    // Log pour debug en cas de problème
    final totalSettlements = validSettlements.fold(0.0, (sum, s) => sum + s.amount);
    _errorService.logInfo('BalanceService', 
        'Règlements calculés: ${validSettlements.length}, Total: ${totalSettlements.toStringAsFixed(2)}€');

    return validSettlements;
  }

  /// Calculer la dette entre deux utilisateurs spécifiques
  double calculateDebtBetweenUsers(String groupId, String userId1, String userId2) {
    // Cette méthode pourrait être utile pour des fonctionnalités avancées
    // comme "Combien me doit X ?" ou "Combien je dois à Y ?"
    return 0.0; // TODO: Implémenter si nécessaire
  }

  /// Analyser les tendances de dépenses par catégorie
  Map<String, double> analyzeCategorySpending(List<Expense> expenses) {
    final categoryTotals = <String, double>{};
    
    for (final expense in expenses) {
      if (expense.isArchived) continue;
      
      final categoryName = expense.category.displayName;
      categoryTotals[categoryName] = (categoryTotals[categoryName] ?? 0) + expense.amountInEur;
    }
    
    return categoryTotals;
  }

  /// Calculer les statistiques du groupe
  GroupStatistics calculateGroupStatistics(List<Expense> expenses) {
    if (expenses.isEmpty) {
      return GroupStatistics.empty();
    }

    final nonArchivedExpenses = expenses.where((e) => !e.isArchived).toList();
    final totalAmount = nonArchivedExpenses.fold(0.0, (sum, e) => sum + e.amountInEur);
    final averageAmount = totalAmount / nonArchivedExpenses.length;
    
    final categorySpending = analyzeCategorySpending(nonArchivedExpenses);
    final topCategory = categorySpending.entries
        .reduce((a, b) => a.value > b.value ? a : b)
        .key;

    return GroupStatistics(
      totalExpenses: totalAmount,
      expenseCount: nonArchivedExpenses.length,
      averageExpense: averageAmount,
      topCategory: topCategory,
      categoryBreakdown: categorySpending,
    );
  }

  Future<void> markSettlementAsCompleted({
    required String groupId,
    required String fromUserId,
    required String toUserId,
    required double amount,
  }) async {
    try {
      // Ici vous pourriez enregistrer le règlement en base
      // ou créer une transaction de règlement
      
      // Pour l'instant, on pourrait juste recalculer
      await Future.delayed(const Duration(milliseconds: 100));
      
      _errorService.logSuccess('BalanceService', 'Règlement marqué comme effectué');
    } catch (e) {
      _errorService.logError('BalanceService', 'Erreur mark settlement: $e');
      rethrow;
    }
  }
}

/// Classe helper pour calculer les balances
class _UserBalanceCalculator {
  final String userId;
  final String userName;
  double _totalPaid = 0.0;
  double _totalOwed = 0.0;

  _UserBalanceCalculator(this.userId, this.userName);

  void addPaid(double amount) => _totalPaid += amount;
  void addOwed(double amount) => _totalOwed += amount;

  UserBalance toUserBalance() {
    return UserBalance(
      userId: userId,
      userName: userName,
      totalPaid: _totalPaid,
      totalOwed: _totalOwed,
      balance: _totalPaid - _totalOwed,
    );
  }
}