import 'package:flutter_bloc/flutter_bloc.dart';
import '../../models/activity.dart';
import '../../repositories/activity_repository.dart';
import '../../services/activity_places_service.dart';
import '../../services/activity_cache_service.dart';
import '../../services/error_service.dart';
import 'activity_event.dart';
import 'activity_state.dart';

/// BLoC for managing activity-related state and operations
class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
  final ActivityRepository _repository;
  final ActivityPlacesService _placesService;
  final ErrorService _errorService;

  ActivityBloc({
    required ActivityRepository repository,
    required ActivityPlacesService placesService,
    required ErrorService errorService,
  }) : _repository = repository,
       _placesService = placesService,
       _errorService = errorService,
       super(const ActivityInitial()) {
    on<LoadActivities>(_onLoadActivities);
    on<LoadTripActivitiesPreservingSearch>(
      _onLoadTripActivitiesPreservingSearch,
    );
    on<SearchActivities>(_onSearchActivities);
    on<SearchActivitiesWithCoordinates>(_onSearchActivitiesWithCoordinates);
    on<SearchActivitiesByText>(_onSearchActivitiesByText);
    on<AddActivity>(_onAddActivity);
    on<AddActivitiesBatch>(_onAddActivitiesBatch);
    on<VoteForActivity>(_onVoteForActivity);
    on<DeleteActivity>(_onDeleteActivity);
    on<FilterActivities>(_onFilterActivities);
    on<RefreshActivities>(_onRefreshActivities);
    on<ClearSearchResults>(_onClearSearchResults);
    on<UpdateActivity>(_onUpdateActivity);
    on<ToggleActivityFavorite>(_onToggleActivityFavorite);
    on<RestoreCachedSearchResults>(_onRestoreCachedSearchResults);
    on<RemoveFromSearchResults>(_onRemoveFromSearchResults);
    on<AddActivityAndRemoveFromSearch>(_onAddActivityAndRemoveFromSearch);
    on<UpdateActivityDate>(_onUpdateActivityDate);
  }

  /// Handles loading activities for a trip
  Future<void> _onLoadActivities(
    LoadActivities event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      emit(const ActivityLoading());

      final activities = await _repository.getActivitiesByTrip(event.tripId);

      emit(
        ActivityLoaded(activities: activities, filteredActivities: activities),
      );
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur chargement activités: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de charger les activités'));
    }
  }

  /// Handles loading trip activities while preserving search results state
  Future<void> _onLoadTripActivitiesPreservingSearch(
    LoadTripActivitiesPreservingSearch event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      final activities = await _repository.getActivitiesByTrip(event.tripId);

      // Si on a un état de recherche actif, on le préserve
      if (state is ActivitySearchResults) {
        // On garde l'état de recherche inchangé, pas besoin d'émettre
        return;
      }

      // Sinon, on charge normalement
      emit(
        ActivityLoaded(activities: activities, filteredActivities: activities),
      );
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur chargement activités: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de charger les activités'));
    }
  }

  Future<void> _onUpdateActivityDate(
    UpdateActivityDate event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      final activity = await _repository.getActivity(
        event.tripId,
        event.activityId,
      );

      if (activity != null) {
        final updatedActivity = activity.copyWith(
          date: event.date,
          clearDate: event.date == null,
        );
        await _repository.updateActivity(updatedActivity);

        // Recharger les activités pour mettre à jour l'UI
        add(LoadActivities(event.tripId));
      }
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur mise à jour date: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de mettre à jour la date'));
    }
  }

  /// Handles searching activities using Google Places API
  Future<void> _onSearchActivities(
    SearchActivities event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      // Si c'est un append (charger plus), on garde l'état actuel et on met isLoading à true
      if (event.appendToExisting && state is ActivitySearchResults) {
        final currentState = state as ActivitySearchResults;
        emit(currentState.copyWith(isLoading: true));
      } else {
        emit(const ActivitySearching());
      }

      final searchResults = await _placesService.searchActivities(
        destination: event.destination,
        tripId: event.tripId,
        category: event.category,
        maxResults:
            event.maxResults ??
            20, // Par défaut 20, ou utiliser la valeur spécifiée
        offset: event.offset ?? 0, // Par défaut 0
      );

      List<Activity> finalResults;

      // Si on doit ajouter aux résultats existants
      if (event.appendToExisting && state is ActivitySearchResults) {
        final currentState = state as ActivitySearchResults;
        finalResults = [...currentState.searchResults, ...searchResults];
      } else {
        finalResults = searchResults;
      }

      // Mettre en cache les résultats
      ActivityCacheService().setCachedActivities(event.tripId, finalResults);

      emit(
        ActivitySearchResults(
          searchResults: finalResults,
          query: event.category?.displayName ?? 'Toutes les activités',
          isLoading: false,
        ),
      );
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur recherche activités: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de rechercher les activités'));
    }
  }

  /// Handles searching activities using coordinates directly (bypasses geocoding)
  Future<void> _onSearchActivitiesWithCoordinates(
    SearchActivitiesWithCoordinates event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      // Si c'est un append (charger plus), on garde l'état actuel et on met isLoading à true
      if (event.appendToExisting && state is ActivitySearchResults) {
        final currentState = state as ActivitySearchResults;
        emit(currentState.copyWith(isLoading: true));
      } else {
        emit(const ActivitySearching());
      }

      final searchResults = await _placesService.searchActivitiesPaginated(
        latitude: event.latitude,
        longitude: event.longitude,
        tripId: event.tripId,
        category: event.category,
        pageSize: event.maxResults ?? 20,
      );

      final activities = searchResults['activities'] as List<Activity>;
      List<Activity> finalResults;

      // Si on doit ajouter aux résultats existants
      if (event.appendToExisting && state is ActivitySearchResults) {
        final currentState = state as ActivitySearchResults;
        finalResults = [...currentState.searchResults, ...activities];
      } else {
        finalResults = activities;
      }

      // Mettre en cache les résultats
      ActivityCacheService().setCachedActivities(event.tripId, finalResults);

      emit(
        ActivitySearchResults(
          searchResults: finalResults,
          query: event.category?.displayName ?? 'Toutes les activités',
          isLoading: false,
        ),
      );
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur recherche activités avec coordonnées: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de rechercher les activités'));
    }
  }

  /// Handles text-based activity search
  Future<void> _onSearchActivitiesByText(
    SearchActivitiesByText event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      emit(const ActivitySearching());

      final searchResults = await _placesService.searchActivitiesByText(
        query: event.query,
        destination: event.destination,
        tripId: event.tripId,
      );

      // Mettre en cache les résultats
      ActivityCacheService().setCachedActivities(event.tripId, searchResults);

      emit(
        ActivitySearchResults(searchResults: searchResults, query: event.query),
      );
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur recherche textuelle: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de rechercher les activités'));
    }
  }

  /// Handles adding a single activity
  Future<void> _onAddActivity(
    AddActivity event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      // Check if activity already exists
      if (event.activity.placeId != null) {
        final existing = await _repository.findExistingActivity(
          event.activity.tripId,
          event.activity.placeId!,
        );

        if (existing != null) {
          emit(const ActivityError('Cette activité a déjà été ajoutée'));
          return;
        }
      }

      final activityId = await _repository.addActivity(event.activity);

      if (activityId != null) {
        // Si on est en état de recherche (suggestions Google), préserver cet état
        if (state is ActivitySearchResults) {
          final currentState = state as ActivitySearchResults;
          // On garde l'état de recherche inchangé mais on ajoute l'info de l'activité ajoutée
          emit(
            currentState.copyWith(
              newlyAddedActivity: event.activity.copyWith(id: activityId),
            ),
          );
          return;
        }

        // Sinon, émettre l'état d'ajout réussi
        emit(
          ActivityAdded(
            activity: event.activity.copyWith(id: activityId),
            message: 'Activité ajoutée avec succès',
          ),
        );
        // Reload activities while preserving search results
        add(LoadTripActivitiesPreservingSearch(event.activity.tripId));
      } else {
        emit(const ActivityError('Impossible d\'ajouter l\'activité'));
      }
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur ajout activité: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible d\'ajouter l\'activité'));
    }
  }

  /// Handles adding an activity and removing it from search results in one action
  Future<void> _onAddActivityAndRemoveFromSearch(
    AddActivityAndRemoveFromSearch event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      // Check if activity already exists
      if (event.activity.placeId != null) {
        final existing = await _repository.findExistingActivity(
          event.activity.tripId,
          event.activity.placeId!,
        );

        if (existing != null) {
          emit(const ActivityError('Cette activité a déjà été ajoutée'));
          return;
        }
      }

      final activityId = await _repository.addActivity(event.activity);

      if (activityId != null) {
        // Si on est en état de recherche (suggestions Google), préserver cet état
        // en supprimant l'activité des résultats
        if (state is ActivitySearchResults) {
          final currentState = state as ActivitySearchResults;
          final updatedResults = currentState.searchResults
              .where((activity) => activity.id != event.googleActivityId)
              .toList();

          emit(
            ActivitySearchResults(
              searchResults: updatedResults,
              query: currentState.query,
              isLoading: false,
            ),
          );
          return;
        }

        // Sinon, émettre l'état d'ajout réussi
        emit(
          ActivityAdded(
            activity: event.activity.copyWith(id: activityId),
            message: 'Activité ajoutée avec succès',
          ),
        );
        // Reload activities while preserving search results
        add(LoadTripActivitiesPreservingSearch(event.activity.tripId));
      } else {
        emit(const ActivityError('Impossible d\'ajouter l\'activité'));
      }
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur ajout activité: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible d\'ajouter l\'activité'));
    }
  }

  /// Handles adding multiple activities in batch
  Future<void> _onAddActivitiesBatch(
    AddActivitiesBatch event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      // Filter out existing activities
      final filteredActivities = <Activity>[];

      emit(
        ActivityBatchAdding(
          activitiesToAdd: event.activities,
          progress: 0,
          total: event.activities.length,
        ),
      );

      for (int i = 0; i < event.activities.length; i++) {
        final activity = event.activities[i];

        if (activity.placeId != null) {
          final existing = await _repository.findExistingActivity(
            activity.tripId,
            activity.placeId!,
          );

          if (existing == null) {
            filteredActivities.add(activity);
          }
        } else {
          filteredActivities.add(activity);
        }

        // Update progress
        emit(
          ActivityBatchAdding(
            activitiesToAdd: event.activities,
            progress: i + 1,
            total: event.activities.length,
          ),
        );
      }

      if (filteredActivities.isEmpty) {
        emit(const ActivityError('Toutes les activités ont déjà été ajoutées'));
        return;
      }

      final addedIds = await _repository.addActivitiesBatch(filteredActivities);

      if (addedIds.isNotEmpty) {
        emit(
          ActivityOperationSuccess(
            '${addedIds.length} activité(s) ajoutée(s) avec succès',
            operationType: 'batch_add',
          ),
        );
        // Reload activities
        add(LoadActivities(event.activities.first.tripId));
      } else {
        emit(const ActivityError('Impossible d\'ajouter les activités'));
      }
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur ajout en lot: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible d\'ajouter les activités'));
    }
  }

  /// Handles voting for an activity
  Future<void> _onVoteForActivity(
    VoteForActivity event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      // Show voting state
      if (state is ActivityLoaded) {
        final currentState = state as ActivityLoaded;
        emit(
          ActivityVoting(
            activityId: event.activityId,
            activities: currentState.activities,
          ),
        );
      }

      final success = await _repository.voteForActivity(
        event.activityId,
        event.userId,
        event.vote,
      );

      if (success) {
        emit(
          ActivityVoteRecorded(
            activityId: event.activityId,
            vote: event.vote,
            userId: event.userId,
          ),
        );

        // Reload activities to reflect the new vote
        if (state is ActivityLoaded) {
          final currentState = state as ActivityLoaded;
          final activities = await _repository.getActivitiesByTrip(
            currentState.activities.first.tripId,
          );

          emit(
            currentState.copyWith(
              activities: activities,
              filteredActivities: _applyFilters(
                activities,
                currentState.activeFilter,
                currentState.minRating,
                currentState.showVotedOnly,
                event.userId,
              ),
            ),
          );
        }
      } else {
        emit(const ActivityError('Impossible d\'enregistrer le vote'));
      }
    } catch (e, stackTrace) {
      _errorService.logError('activity_bloc', 'Erreur vote: $e', stackTrace);
      emit(const ActivityError('Impossible d\'enregistrer le vote'));
    }
  }

  /// Handles deleting an activity
  Future<void> _onDeleteActivity(
    DeleteActivity event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      final success = await _repository.deleteActivity(event.activityId);

      if (success) {
        emit(
          ActivityDeleted(
            activityId: event.activityId,
            message: 'Activité supprimée avec succès',
          ),
        );

        // Reload if we're on the activity list
        if (state is ActivityLoaded) {
          final currentState = state as ActivityLoaded;
          if (currentState.activities.isNotEmpty) {
            add(LoadActivities(currentState.activities.first.tripId));
          }
        }
      } else {
        emit(const ActivityError('Impossible de supprimer l\'activité'));
      }
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur suppression: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de supprimer l\'activité'));
    }
  }

  /// Handles filtering activities
  Future<void> _onFilterActivities(
    FilterActivities event,
    Emitter<ActivityState> emit,
  ) async {
    if (state is ActivityLoaded) {
      final currentState = state as ActivityLoaded;

      final filteredActivities = _applyFilters(
        currentState.activities,
        event.category,
        event.minRating,
        event.showVotedOnly ?? false,
        '', // UserId would be needed for showVotedOnly filter
      );

      emit(
        currentState.copyWith(
          filteredActivities: filteredActivities,
          activeFilter: event.category,
          minRating: event.minRating,
          showVotedOnly: event.showVotedOnly ?? false,
        ),
      );
    }
  }

  /// Handles refreshing activities
  Future<void> _onRefreshActivities(
    RefreshActivities event,
    Emitter<ActivityState> emit,
  ) async {
    add(LoadActivities(event.tripId));
  }

  /// Handles clearing search results
  Future<void> _onClearSearchResults(
    ClearSearchResults event,
    Emitter<ActivityState> emit,
  ) async {
    if (state is ActivitySearchResults) {
      emit(const ActivityInitial());
    }
  }

  /// Handles updating an activity
  Future<void> _onUpdateActivity(
    UpdateActivity event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      if (state is ActivityLoaded) {
        final currentState = state as ActivityLoaded;
        emit(
          ActivityUpdating(
            activityId: event.activity.id,
            activities: currentState.activities,
          ),
        );
      }

      final success = await _repository.updateActivity(event.activity);

      if (success) {
        emit(
          const ActivityOperationSuccess(
            'Activité mise à jour avec succès',
            operationType: 'update',
          ),
        );

        // Reload activities
        add(LoadActivities(event.activity.tripId));
      } else {
        emit(const ActivityError('Impossible de mettre à jour l\'activité'));
      }
    } catch (e, stackTrace) {
      _errorService.logError(
        'activity_bloc',
        'Erreur mise à jour: $e',
        stackTrace,
      );
      emit(const ActivityError('Impossible de mettre à jour l\'activité'));
    }
  }

  /// Handles toggling activity favorite status
  Future<void> _onToggleActivityFavorite(
    ToggleActivityFavorite event,
    Emitter<ActivityState> emit,
  ) async {
    try {
      // This would require extending the Activity model to include favorites
      // For now, we'll use the voting system as a favorite system
      add(
        VoteForActivity(
          activityId: event.activityId,
          userId: event.userId,
          vote: 1,
        ),
      );
    } catch (e, stackTrace) {
      _errorService.logError('activity_bloc', 'Erreur favori: $e', stackTrace);
      emit(const ActivityError('Impossible de modifier les favoris'));
    }
  }

  /// Applies filters to the activities list
  List<Activity> _applyFilters(
    List<Activity> activities,
    String? category,
    double? minRating,
    bool showVotedOnly,
    String userId,
  ) {
    var filtered = activities;

    if (category != null) {
      filtered = filtered.where((a) {
        // Check exact match (internal value)
        if (a.category == category) return true;

        // Check display name match
        // Find the enum that matches the filter category (which is a display name)
        try {
          final categoryEnum = ActivityCategory.values.firstWhere(
            (e) => e.displayName == category,
          );
          // Check if activity category matches the enum's google type or display name
          return a.category == categoryEnum.googlePlaceType ||
              a.category == categoryEnum.displayName ||
              a.category.toLowerCase() == categoryEnum.name.toLowerCase();
        } catch (_) {
          // If no matching enum found, fallback to simple string comparison
          return a.category.toLowerCase() == category.toLowerCase();
        }
      }).toList();
    }

    if (minRating != null) {
      filtered = filtered.where((a) => (a.rating ?? 0) >= minRating).toList();
    }

    if (showVotedOnly && userId.isNotEmpty) {
      filtered = filtered.where((a) => a.hasUserVoted(userId)).toList();
    }

    return filtered;
  }

  /// Removes an activity from search results
  Future<void> _onRemoveFromSearchResults(
    RemoveFromSearchResults event,
    Emitter<ActivityState> emit,
  ) async {
    // Si on est actuellement dans un état de résultats de recherche
    if (state is ActivitySearchResults) {
      final currentState = state as ActivitySearchResults;

      // Filtrer l'activité à retirer
      final updatedResults = currentState.searchResults
          .where((activity) => activity.id != event.activityId)
          .toList();

      // Émettre le nouvel état avec l'activité retirée
      emit(
        ActivitySearchResults(
          searchResults: updatedResults,
          query: currentState.query,
          isLoading: false,
        ),
      );
    }
  }

  /// Restores cached search results
  Future<void> _onRestoreCachedSearchResults(
    RestoreCachedSearchResults event,
    Emitter<ActivityState> emit,
  ) async {
    emit(
      ActivitySearchResults(
        searchResults: event.searchResults,
        query: 'cached',
        isLoading: false,
      ),
    );
  }
}
