/// BLoC pour la gestion des membres
library membres_bloc;

import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'membres_event.dart';
import 'membres_state.dart';
import '../../../shared/models/membre_search_criteria.dart';
import '../../../shared/models/membre_search_result.dart';
import '../domain/usecases/get_members.dart';
import '../domain/usecases/get_member_by_id.dart';
import '../domain/usecases/create_member.dart' as uc;
import '../domain/usecases/update_member.dart' as uc;
import '../domain/usecases/delete_member.dart' as uc;
import '../domain/usecases/search_members.dart';
import '../domain/usecases/get_member_stats.dart';
import '../domain/repositories/membre_repository.dart';
import '../../../core/websocket/websocket_service.dart';
import '../../../core/utils/logger.dart';

/// BLoC pour la gestion des membres (Clean Architecture)
@injectable
class MembresBloc extends Bloc<MembresEvent, MembresState> {
  final GetMembers _getMembers;
  final GetMemberById _getMemberById;
  final uc.CreateMember _createMember;
  final uc.UpdateMember _updateMember;
  final uc.DeleteMember _deleteMember;
  final SearchMembers _searchMembers;
  final GetMemberStats _getMemberStats;
  final IMembreRepository _repository;
  final WebSocketService _webSocketService;

  StreamSubscription<WebSocketEvent>? _webSocketSubscription;

  MembresBloc(
    this._getMembers,
    this._getMemberById,
    this._createMember,
    this._updateMember,
    this._deleteMember,
    this._searchMembers,
    this._getMemberStats,
    this._repository,
    this._webSocketService,
  ) : super(const MembresInitial()) {
    on<LoadMembres>(_onLoadMembres);
    on<LoadMembreById>(_onLoadMembreById);
    on<CreateMembre>(_onCreateMembre);
    on<UpdateMembre>(_onUpdateMembre);
    on<DeleteMembre>(_onDeleteMembre);
    on<ActivateMembre>(_onActivateMembre);
    on<DeactivateMembre>(_onDeactivateMembre);
    on<SearchMembres>(_onSearchMembres);
    on<LoadActiveMembres>(_onLoadActiveMembres);
    on<LoadBureauMembres>(_onLoadBureauMembres);
    on<LoadMembresStats>(_onLoadMembresStats);
    on<ResetMotDePasse>(_onResetMotDePasse);
    on<AffecterOrganisation>(_onAffecterOrganisation);
    on<InviterMembre>(_onInviterMembre);
    on<ActiverAdhesion>(_onActiverAdhesion);
    on<SuspendrAdhesion>(_onSuspendrAdhesion);
    on<RadierAdhesion>(_onRadierAdhesion);

    _initWebSocketListener();
  }

  void _initWebSocketListener() {
    _webSocketSubscription = _webSocketService.eventStream.listen(
      (event) {
        try {
          if (event is MemberEvent) {
            AppLogger.info('MembresBloc: MemberEvent reçu (${event.eventType}), refresh liste');
            final currentState = state;
            if (currentState is MembresLoaded && !isClosed) {
              add(LoadMembres(
                refresh: true,
                organisationId: currentState.organisationId,
                page: currentState.currentPage,
                size: currentState.pageSize,
              ));
            }
          }
        } catch (e, s) {
          AppLogger.error('MembresBloc: erreur lors du traitement WebSocket event', error: e);
        }
      },
      onError: (error) => AppLogger.error('MembresBloc: WebSocket stream error', error: error),
    );
  }

  @override
  Future<void> close() {
    _webSocketSubscription?.cancel();
    return super.close();
  }

  /// Charge la liste des membres
  Future<void> _onLoadMembres(
    LoadMembres event,
    Emitter<MembresState> emit,
  ) async {
    try {
      // Si refresh et qu'on a déjà des données, on garde l'état actuel
      if (event.refresh && state is MembresLoaded) {
        final currentState = state as MembresLoaded;
        emit(MembresRefreshing(currentState.membres));
      } else {
        emit(const MembresLoading());
      }

      final MembreSearchResult result;
      if (event.organisationId != null) {
        // OrgAdmin : scope la requête à son organisation via la recherche avancée.
        // includeInactifs=true pour récupérer aussi les membres "En attente"
        // (actif=false) — le filtrage par statut est géré côté UI.
        result = await _searchMembers(
          criteria: MembreSearchCriteria(
            organisationIds: [event.organisationId!],
            query: event.recherche?.isNotEmpty == true ? event.recherche : null,
            includeInactifs: true,
          ),
          page: event.page,
          size: event.size,
        );
      } else {
        // SuperAdmin et autres rôles : accès global sans filtre org
        result = await _getMembers(
          page: event.page,
          size: event.size,
          recherche: event.recherche,
        );
      }

      emit(MembresLoaded(
        membres: result.membres,
        totalElements: result.totalElements,
        currentPage: result.currentPage,
        pageSize: result.pageSize,
        totalPages: result.totalPages,
        organisationId: event.organisationId,
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors du chargement des membres. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Charge un membre par ID
  Future<void> _onLoadMembreById(
    LoadMembreById event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final membre = await _getMemberById(event.id);

      if (membre != null) {
        emit(MembreDetailLoaded(membre));
      } else {
        emit(const MembresError(
          message: 'Membre non trouvé',
          code: '404',
        ));
      }
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors du chargement du membre. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Crée un nouveau membre
  Future<void> _onCreateMembre(
    CreateMembre event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final membre = await _createMember(event.membre);

      emit(MembreCreated(membre));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      if (e.response?.statusCode == 400) {
        // Erreur de validation
        final errors = _extractValidationErrors(e.response?.data);
        emit(MembresValidationError(
          message: 'Erreur de validation',
          validationErrors: errors,
          code: '400',
        ));
      } else {
        emit(MembresNetworkError(
          message: _getNetworkErrorMessage(e),
          code: e.response?.statusCode.toString(),
          error: e,
        ));
      }
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de la création du membre. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Met à jour un membre
  Future<void> _onUpdateMembre(
    UpdateMembre event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final membre = await _updateMember(event.id, event.membre);

      emit(MembreUpdated(membre));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      if (e.response?.statusCode == 400) {
        final errors = _extractValidationErrors(e.response?.data);
        emit(MembresValidationError(
          message: 'Erreur de validation',
          validationErrors: errors,
          code: '400',
        ));
      } else {
        emit(MembresNetworkError(
          message: _getNetworkErrorMessage(e),
          code: e.response?.statusCode.toString(),
          error: e,
        ));
      }
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de la mise à jour du membre. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Supprime un membre
  Future<void> _onDeleteMembre(
    DeleteMembre event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      await _deleteMember(event.id);

      emit(MembreDeleted(event.id));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      // 409 Conflict = mono-admin orphelinage détecté par le backend.
      // Émettre MembresActionForbidden avec le message métier du backend.
      if (e.response?.statusCode == 409 || e.response?.statusCode == 403) {
        final msg = _extractErrorMessage(e)
            ?? 'Suppression impossible : vérifiez que le membre n\'est pas le seul admin d\'une organisation.';
        emit(MembresActionForbidden(message: msg, error: e));
        return;
      }
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de la suppression du membre. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Active un membre
  Future<void> _onActivateMembre(
    ActivateMembre event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final membre = await _repository.activateMembre(event.id);

      emit(MembreActivated(membre));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de l\'activation du membre. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Désactive un membre
  Future<void> _onDeactivateMembre(
    DeactivateMembre event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final membre = await _repository.deactivateMembre(event.id);

      emit(MembreDeactivated(membre));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de la désactivation du membre. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Réinitialise le mot de passe d'un membre
  Future<void> _onResetMotDePasse(
    ResetMotDePasse event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final membre = await _repository.resetMotDePasse(event.id);

      emit(MotDePasseReinitialise(membre));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de la réinitialisation du mot de passe. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Affecte un membre à une organisation (superadmin)
  Future<void> _onAffecterOrganisation(
    AffecterOrganisation event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final membre = await _repository.affecterOrganisation(event.membreId, event.organisationId);

      emit(MembreAffecte(membre));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de l\'affectation à l\'organisation. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Recherche avancée de membres
  Future<void> _onSearchMembres(
    SearchMembres event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final result = await _searchMembers(
        criteria: event.criteria,
        page: event.page,
        size: event.size,
      );

      emit(MembresLoaded(
        membres: result.membres,
        totalElements: result.totalElements,
        currentPage: result.currentPage,
        pageSize: result.pageSize,
        totalPages: result.totalPages,
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors de la recherche de membres. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Charge les membres actifs
  Future<void> _onLoadActiveMembres(
    LoadActiveMembres event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

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

      emit(MembresLoaded(
        membres: result.membres,
        totalElements: result.totalElements,
        currentPage: result.currentPage,
        pageSize: result.pageSize,
        totalPages: result.totalPages,
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors du chargement des membres actifs. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Charge les membres du bureau
  Future<void> _onLoadBureauMembres(
    LoadBureauMembres event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

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

      emit(MembresLoaded(
        membres: result.membres,
        totalElements: result.totalElements,
        currentPage: result.currentPage,
        pageSize: result.pageSize,
        totalPages: result.totalPages,
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors du chargement des membres du bureau. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Charge les statistiques
  Future<void> _onLoadMembresStats(
    LoadMembresStats event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());

      final stats = await _getMemberStats();

      emit(MembresStatsLoaded(stats));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) return;
      emit(MembresError(
        message: 'Erreur lors du chargement des statistiques. Veuillez réessayer.',
        error: e,
      ));
    }
  }

  /// Extrait les erreurs de validation de la réponse
  Map<String, String> _extractValidationErrors(dynamic data) {
    final errors = <String, String>{};
    if (data is Map<String, dynamic> && data.containsKey('errors')) {
      final errorsData = data['errors'];
      if (errorsData is Map<String, dynamic>) {
        errorsData.forEach((key, value) {
          errors[key] = value.toString();
        });
      }
    }
    return errors;
  }

  /// Génère un message d'erreur réseau approprié
  String _getNetworkErrorMessage(DioException e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
        return 'Délai de connexion dépassé. Vérifiez votre connexion internet.';
      case DioExceptionType.sendTimeout:
        return 'Délai d\'envoi dépassé. Vérifiez votre connexion internet.';
      case DioExceptionType.receiveTimeout:
        return 'Délai de réception dépassé. Vérifiez votre connexion internet.';
      case DioExceptionType.badResponse:
        final statusCode = e.response?.statusCode;
        if (statusCode == 401) {
          return 'Non autorisé. Veuillez vous reconnecter.';
        } else if (statusCode == 403) {
          return 'Accès refusé. Vous n\'avez pas les permissions nécessaires.';
        } else if (statusCode == 404) {
          return 'Ressource non trouvée.';
        } else if (statusCode == 409) {
          return 'Conflit. Cette ressource existe déjà.';
        } else if (statusCode != null && statusCode >= 500) {
          return 'Erreur serveur. Veuillez réessayer plus tard.';
        }
        return 'Erreur lors de la communication avec le serveur.';
      case DioExceptionType.cancel:
        return 'Requête annulée.';
      case DioExceptionType.unknown:
        return 'Erreur de connexion. Vérifiez votre connexion internet.';
      default:
        return 'Erreur réseau inattendue.';
    }
  }

  /// Extrait le message d'erreur du corps JSON d'une réponse HTTP (champ "error"
  /// ou "message"). Retourne null si non disponible.
  String? _extractErrorMessage(DioException e) {
    try {
      final data = e.response?.data;
      if (data is Map) {
        return (data['message'] ?? data['error'])?.toString();
      }
    } catch (_) {}
    return null;
  }

  // ── Handlers cycle de vie des adhésions ──────────────────────────────────

  Future<void> _onInviterMembre(
    InviterMembre event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());
      final result = await _repository.inviterMembre(
        event.membreId,
        event.organisationId,
        roleOrg: event.roleOrg,
      );
      emit(MembreInvite(
        membreId: event.membreId,
        organisationId: event.organisationId,
        nouveauStatut: result['statut']?.toString() ?? 'INVITE',
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      emit(MembresError(message: 'Erreur lors de l\'invitation du membre.', error: e));
    }
  }

  Future<void> _onActiverAdhesion(
    ActiverAdhesion event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());
      final result = await _repository.activerAdhesion(
        event.membreId,
        event.organisationId,
        motif: event.motif,
      );
      emit(AdhesionActivee(
        membreId: event.membreId,
        nouveauStatut: result['statut']?.toString() ?? 'ACTIF',
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      if (e.response?.statusCode == 403) {
        final msg = _extractErrorMessage(e) ?? 'Action non autorisée sur ce membre.';
        emit(MembresActionForbidden(message: msg, error: e));
        return;
      }
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      emit(MembresError(message: 'Erreur lors de l\'activation de l\'adhésion.', error: e));
    }
  }

  Future<void> _onSuspendrAdhesion(
    SuspendrAdhesion event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());
      final result = await _repository.suspendrAdhesion(
        event.membreId,
        event.organisationId,
        motif: event.motif,
      );
      emit(AdhesionSuspendue(
        membreId: event.membreId,
        nouveauStatut: result['statut']?.toString() ?? 'SUSPENDU',
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      if (e.response?.statusCode == 403) {
        final msg = _extractErrorMessage(e) ?? 'Action non autorisée sur ce membre.';
        emit(MembresActionForbidden(message: msg, error: e));
        return;
      }
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      emit(MembresError(message: 'Erreur lors de la suspension de l\'adhésion.', error: e));
    }
  }

  Future<void> _onRadierAdhesion(
    RadierAdhesion event,
    Emitter<MembresState> emit,
  ) async {
    try {
      emit(const MembresLoading());
      final result = await _repository.radierAdhesion(
        event.membreId,
        event.organisationId,
        motif: event.motif,
      );
      emit(MembreRadie(
        membreId: event.membreId,
        nouveauStatut: result['statut']?.toString() ?? 'RADIE',
      ));
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) return;
      if (e.response?.statusCode == 403) {
        final msg = _extractErrorMessage(e) ?? 'Action non autorisée sur ce membre.';
        emit(MembresActionForbidden(message: msg, error: e));
        return;
      }
      emit(MembresNetworkError(
        message: _getNetworkErrorMessage(e),
        code: e.response?.statusCode.toString(),
        error: e,
      ));
    } catch (e) {
      emit(MembresError(message: 'Erreur lors de la radiation du membre.', error: e));
    }
  }
}

