/// BLoC pour la gestion des contributions
library contributions_bloc;

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import '../../../core/utils/logger.dart';
import '../data/models/contribution_model.dart';
import '../data/repositories/contribution_repository.dart' show ContributionPageResult;
import '../domain/usecases/get_contributions.dart';
import '../domain/usecases/get_contribution_by_id.dart';
import '../domain/usecases/create_contribution.dart' as uc;
import '../domain/usecases/update_contribution.dart' as uc;
import '../domain/usecases/delete_contribution.dart' as uc;
import '../domain/usecases/pay_contribution.dart';
import '../domain/usecases/get_contribution_stats.dart';
import '../domain/repositories/contribution_repository.dart';
import 'contributions_event.dart';
import 'contributions_state.dart';

/// BLoC pour gérer l'état des contributions via les use cases (Clean Architecture)
@injectable
class ContributionsBloc extends Bloc<ContributionsEvent, ContributionsState> {
  final GetContributions _getContributions;
  final GetContributionById _getContributionById;
  final uc.CreateContribution _createContribution;
  final uc.UpdateContribution _updateContribution;
  final uc.DeleteContribution _deleteContribution;
  final PayContribution _payContribution;
  final GetContributionStats _getContributionStats;
  final IContributionRepository _repository; // Pour méthodes non-couvertes par use cases

  ContributionsBloc(
    this._getContributions,
    this._getContributionById,
    this._createContribution,
    this._updateContribution,
    this._deleteContribution,
    this._payContribution,
    this._getContributionStats,
    this._repository,
  ) : super(const ContributionsInitial()) {
    on<LoadContributions>(_onLoadContributions);
    on<LoadContributionById>(_onLoadContributionById);
    on<CreateContribution>(_onCreateContribution);
    on<UpdateContribution>(_onUpdateContribution);
    on<DeleteContribution>(_onDeleteContribution);
    on<SearchContributions>(_onSearchContributions);
    on<LoadContributionsByMembre>(_onLoadContributionsByMembre);
    on<LoadContributionsPayees>(_onLoadContributionsPayees);
    on<LoadContributionsNonPayees>(_onLoadContributionsNonPayees);
    on<LoadContributionsEnRetard>(_onLoadContributionsEnRetard);
    on<RecordPayment>(_onRecordPayment);
    on<LoadContributionsStats>(_onLoadContributionsStats);
    on<GenerateAnnualContributions>(_onGenerateAnnualContributions);
    on<SendPaymentReminder>(_onSendPaymentReminder);
  }

  Future<void> _onLoadContributions(
    LoadContributions event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      AppLogger.blocEvent('ContributionsBloc', 'LoadContributions', data: {
        'page': event.page,
        'size': event.size,
      });

      emit(const ContributionsLoading(message: 'Chargement des contributions...'));

      // Use case: Get contributions
      final result = await _getContributions(page: event.page, size: event.size);

      emit(ContributionsLoaded(
        contributions: result.contributions,
        total: result.total,
        page: result.page,
        size: result.size,
        totalPages: result.totalPages,
      ));

      AppLogger.blocState('ContributionsBloc', 'ContributionsLoaded', data: {
        'count': result.contributions.length,
        'total': result.total,
      });
    } catch (e, stackTrace) {
      AppLogger.error('Erreur lors du chargement des contributions', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur lors du chargement des contributions', error: e));
    }
  }

  Future<void> _onLoadContributionById(
    LoadContributionById event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Chargement de la contribution...'));
      final contribution = await _getContributionById(event.id);
      emit(ContributionDetailLoaded(contribution: contribution));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Contribution non trouvée', error: e));
    }
  }

  Future<void> _onCreateContribution(
    CreateContribution event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Création de la contribution...'));
      final created = await _createContribution(event.contribution);
      emit(ContributionCreated(contribution: created));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur lors de la création de la contribution', error: e));
    }
  }

  Future<void> _onUpdateContribution(
    UpdateContribution event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Mise à jour de la contribution...'));
      final updated = await _updateContribution(event.id, event.contribution);
      emit(ContributionUpdated(contribution: updated));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur lors de la mise à jour', error: e));
    }
  }

  Future<void> _onDeleteContribution(
    DeleteContribution event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Suppression de la contribution...'));
      await _deleteContribution(event.id);
      emit(ContributionDeleted(id: event.id));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur lors de la suppression', error: e));
    }
  }

  Future<void> _onSearchContributions(
    SearchContributions event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Recherche en cours...'));

      final result = await _repository.getCotisations(
        page: event.page,
        size: event.size,
        membreId: event.membreId,
        statut: event.statut?.name,
        type: event.type?.name,
        annee: event.annee,
      );

      emit(ContributionsLoaded(
        contributions: result.contributions,
        total: result.total,
        page: result.page,
        size: result.size,
        totalPages: result.totalPages,
      ));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur lors de la recherche', error: e));
    }
  }

  Future<void> _onLoadContributionsByMembre(
    LoadContributionsByMembre event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Chargement des contributions du membre...'));

      final result = await _repository.getCotisations(
        page: event.page,
        size: event.size,
        membreId: event.membreId,
      );

      emit(ContributionsLoaded(
        contributions: result.contributions,
        total: result.total,
        page: result.page,
        size: result.size,
        totalPages: result.totalPages,
      ));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur lors du chargement', error: e));
    }
  }

  Future<void> _onLoadContributionsPayees(
    LoadContributionsPayees event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Chargement des contributions payées...'));
      final result = await _repository.getMesCotisations();
      final payees = result.contributions.where((c) => c.statut == ContributionStatus.payee).toList();
      emit(ContributionsLoaded(
        contributions: payees,
        total: payees.length,
        page: 0,
        size: payees.length,
        totalPages: payees.isEmpty ? 0 : 1,
      ));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur', error: e));
    }
  }

  Future<void> _onLoadContributionsNonPayees(
    LoadContributionsNonPayees event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Chargement des contributions non payées...'));
      final result = await _repository.getMesCotisations();
      final nonPayees = result.contributions.where((c) => c.statut != ContributionStatus.payee).toList();
      emit(ContributionsLoaded(
        contributions: nonPayees,
        total: nonPayees.length,
        page: 0,
        size: nonPayees.length,
        totalPages: nonPayees.isEmpty ? 0 : 1,
      ));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur', error: e));
    }
  }

  Future<void> _onLoadContributionsEnRetard(
    LoadContributionsEnRetard event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Chargement des contributions en retard...'));
      final result = await _repository.getMesCotisations();
      final enRetard = result.contributions.where((c) => c.statut == ContributionStatus.enRetard || c.estEnRetard).toList();
      emit(ContributionsLoaded(
        contributions: enRetard,
        total: enRetard.length,
        page: 0,
        size: enRetard.length,
        totalPages: enRetard.isEmpty ? 0 : 1,
      ));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur', error: e));
    }
  }

  Future<void> _onRecordPayment(
    RecordPayment event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Enregistrement du paiement...'));

      final updated = await _payContribution(
        cotisationId: event.contributionId,
        montant: event.montant,
        datePaiement: event.datePaiement,
        methodePaiement: event.methodePaiement.name,
        numeroPaiement: event.numeroPaiement,
        referencePaiement: event.referencePaiement,
      );

      emit(PaymentRecorded(contribution: updated));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur lors de l\'enregistrement du paiement', error: e));
    }
  }

  Future<void> _onLoadContributionsStats(
    LoadContributionsStats event,
    Emitter<ContributionsState> emit,
  ) async {
    List<ContributionModel>? preservedList = state is ContributionsLoaded ? (state as ContributionsLoaded).contributions : null;
    try {
      // Charger synthèse + liste pour que la page « Mes statistiques » ait toujours donut et prochaines échéances
      final mesSynthese = await _getContributionStats();
      final listResult = preservedList == null ? await _getContributions() : null;
      final contributions = preservedList ?? listResult?.contributions;

      if (mesSynthese != null && mesSynthese.isNotEmpty) {
        final normalized = _normalizeSyntheseForStats(mesSynthese);
        emit(ContributionsStatsLoaded(stats: normalized, contributions: contributions));
        return;
      }
      final stats = await _repository.getStatistiques();
      emit(ContributionsStatsLoaded(
        stats: stats.map((k, v) => MapEntry(k, v is num ? v.toDouble() : (v is int ? v.toDouble() : 0.0))),
        contributions: contributions,
      ));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur', error: e));
    }
  }

  /// Normalise la réponse synthese (mes) pour l'affichage stats (clés numériques + isMesSynthese).
  Map<String, dynamic> _normalizeSyntheseForStats(Map<String, dynamic> s) {
    final montantDu = _toDouble(s['montantDu']);
    final totalPayeAnnee = _toDouble(s['totalPayeAnnee']);
    final totalAnnee = montantDu + totalPayeAnnee;
    final taux = totalAnnee > 0 ? (totalPayeAnnee / totalAnnee * 100) : 0.0;
    return {
      'isMesSynthese': true,
      'cotisationsEnAttente': (s['cotisationsEnAttente'] is int) ? s['cotisationsEnAttente'] as int : ((s['cotisationsEnAttente'] as num?)?.toInt() ?? 0),
      'montantDu': montantDu,
      'totalPayeAnnee': totalPayeAnnee,
      'totalMontant': totalAnnee,
      'tauxPaiement': taux,
      'prochaineEcheance': s['prochaineEcheance']?.toString(),
      'anneeEnCours': s['anneeEnCours'] is int ? s['anneeEnCours'] as int : ((s['anneeEnCours'] as num?)?.toInt() ?? DateTime.now().year),
    };
  }

  double _toDouble(dynamic v) {
    if (v == null) return 0;
    if (v is num) return v.toDouble();
    if (v is String) return double.tryParse(v) ?? 0;
    return 0;
  }

  Future<void> _onGenerateAnnualContributions(
    GenerateAnnualContributions event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Génération des contributions...'));
      final count = await _repository.genererCotisationsAnnuelles(event.annee);
      emit(ContributionsGenerated(nombreGenere: count));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur', error: e));
    }
  }

  Future<void> _onSendPaymentReminder(
    SendPaymentReminder event,
    Emitter<ContributionsState> emit,
  ) async {
    try {
      emit(const ContributionsLoading(message: 'Envoi du rappel...'));
      await _repository.envoyerRappel(event.contributionId);
      emit(ReminderSent(contributionId: event.contributionId));
    } catch (e, stackTrace) {
      AppLogger.error('Erreur', error: e, stackTrace: stackTrace);
      emit(ContributionsError(message: 'Erreur', error: e));
    }
  }
}
