import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart';
import 'package:krow/core/application/clients/api/api_exception.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/data/models/staff/pivot.dart';
import 'package:krow/core/entity/event_entity.dart';
import 'package:krow/core/entity/position_entity.dart';
import 'package:krow/core/entity/shift_entity.dart';
import 'package:krow/core/entity/staff_contact_entity.dart';
import 'package:krow/core/sevices/create_event_service/create_event_service.dart';
import 'package:krow/core/sevices/geofencin_service.dart';
import 'package:krow/features/events/domain/events_repository.dart';
import 'package:meta/meta.dart';

part 'event_details_event.dart';
part 'event_details_state.dart';

class EventDetailsBloc extends Bloc<EventDetailsEvent, EventDetailsState> {
  final EventsRepository _repository;
  Timer? _timer;
  var disabledPolling = false;

  EventDetailsBloc(EventEntity event, this._repository, isPreview)
      : super(EventDetailsState(event: event, isPreview: isPreview)) {

    on<EventDetailsInitialEvent>(_onInit);
    on<EventDetailsUpdateEvent>(_onUpdateEntity);
    on<OnShiftHeaderTapEvent>(_onShiftHeaderTap);
    on<OnRoleHeaderTapEvent>(_onRoleHeaderTap);
    on<OnAssignedStaffHeaderTapEvent>(_onAssignedStaffHeaderTap);
    on<CompleteEventEvent>(_onCompleteEvent);
    on<CancelClientEvent>(_onCancelClientEvent);
    on<TrackClientClockin>(_onTrackClientClockin);
    on<TrackClientClockout>(_onTrackClientClockout);
    on<NotShowedPositionStaffEvent>(_onNotShowedPositionStaff);
    on<ReplacePositionStaffEvent>(_onReplacePositionStaff);
    on<CreateEventPostEvent>(_createEvent);
    on<DetailsDeleteDraftEvent>(_onDetailsDeleteDraftEvent);
    on<DetailsPublishEvent>(_onDetailsPublishEvent);
    on<GeofencingEvent>(_onGeofencingEvent);
    on<RefreshEventDetailsEvent>(_onRefreshEventDetailsEvent);
    on<DisablePollingEvent>(_onDisablePollingEvent);
    on<EnablePollingEvent>(_onEnablePollingEvent);
  }

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

  FutureOr<void> _onShiftHeaderTap(OnShiftHeaderTapEvent event, emit) {
    emit(state.copyWith(
        shifts: state.shifts
            .map((e) =>
                e.copyWith(isExpanded: e == event.shiftState && !e.isExpanded))
            .toList()));
  }

  FutureOr<void> _onRoleHeaderTap(OnRoleHeaderTapEvent event, emit) {
    emit(state.copyWith(
        shifts: state.shifts
            .map((e) => e.copyWith(
                positions: e.positions
                    .map((r) => r.copyWith(
                        isExpanded:
                            r.position.id == event.roleState.position.id &&
                                !r.isExpanded))
                    .toList()))
            .toList()));
  }

  FutureOr<void> _onAssignedStaffHeaderTap(
      OnAssignedStaffHeaderTapEvent event, emit) {
    emit(state.copyWith(
        shifts: state.shifts
            .map((e) => e.copyWith(
                positions: e.positions
                    .map((r) => r.copyWith(
                        isStaffExpanded:
                            r.position.id == event.roleState.position.id &&
                                !r.isStaffExpanded))
                    .toList()))
            .toList()));
  }

  FutureOr<void> _onCompleteEvent(CompleteEventEvent event, emit) async {
    try {
      emit(state.copyWith(inLoading: true));
      await _repository.completeClientEvent(state.event.id,
          comment: event.comment);
      emit(state.copyWith(
          event: state.event.copyWith(status: EventStatus.completed)));
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(showErrorPopup: e.message));
        return;
      }
    }
    emit(state.copyWith(inLoading: false));

  }

  FutureOr<void> _onCancelClientEvent(CancelClientEvent event, emit) async {
    emit(state.copyWith(inLoading: true));
    try {
      await _repository.cancelClientEvent(state.event.id, state.event.status);
      emit(state.copyWith(
          event: state.event.copyWith(status: EventStatus.canceled)));
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(showErrorPopup: e.message));
      }
    }
    emit(state.copyWith(inLoading: false));
  }

  FutureOr<void> _onInit(EventDetailsInitialEvent event, emit) {
    getIt<CreateEventService>().eventsRepository = _repository;
    emit(state.copyWith(
      inLoading: true,
      shifts: state.event.shifts
              ?.map((shift) => ShiftState(
                  shift: shift,
                  positions: shift.positions
                      .map((position) => PositionState(position: position))
                      .toList()))
              .toList() ??
          [],
    ));

    add(RefreshEventDetailsEvent());



    if (!state.isPreview) {
      startPoling();
    }else{
      emit(state.copyWith(inLoading: false));
    }
  }

  FutureOr<void> _onTrackClientClockin(TrackClientClockin event, emit) async {
    emit(state.copyWith(inLoading: true));
    if (!(await checkGeofancing(event.staffContact))) {
      emit(state.copyWith(inLoading: false));
      return;
    }
    try {
      await _repository.trackClientClockin(event.staffContact.id);
      event.staffContact.status = PivotStatus.ongoing;
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(showErrorPopup: e.message));
      }
    }
    emit(state.copyWith(inLoading: false));
  }

  FutureOr<void> _onTrackClientClockout(TrackClientClockout event, emit) async {
    emit(state.copyWith(inLoading: true));
    try {
      await _repository.trackClientClockout(event.staffContact.id);
      event.staffContact.status = PivotStatus.confirmed;
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(showErrorPopup: e.message));
      }
    }
    emit(state.copyWith(inLoading: false));
  }

  FutureOr<void> _onNotShowedPositionStaff(
      NotShowedPositionStaffEvent event, emit) async {
    emit(state.copyWith(inLoading: true));
    try {
      await _repository.notShowedPositionStaff(event.positionStaffId);
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(showErrorPopup: e.message));
      }
    }
    emit(state.copyWith(inLoading: false));
  }

  FutureOr<void> _onReplacePositionStaff(
      ReplacePositionStaffEvent event, emit) async {
    emit(state.copyWith(inLoading: true));
    try {
      await _repository.replacePositionStaff(
          event.positionStaffId, event.reason);
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(showErrorPopup: e.message));
      }else{
        emit(state.copyWith(
            showErrorPopup: 'Something went wrong'));
      }
    }
    emit(state.copyWith(inLoading: false));
  }

  void _createEvent(
      CreateEventPostEvent event, Emitter<EventDetailsState> emit) async {
    emit(state.copyWith(inLoading: true));
    try {
      if (state.event.id.isEmpty) {
        await getIt<CreateEventService>().createEventService(
          state.event,
        );
      } else {
        await getIt<CreateEventService>().updateEvent(
          state.event,
        );
      }
      // emit(state.copyWith(inLoading: false, ));
      emit(state.copyWith(inLoading: false, needDeepPop: true));
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
        return;
      } else {
        print(e);
        emit(state.copyWith(
            inLoading: false, showErrorPopup: 'Something went wrong'));
        return;
      }
    }
    emit(state.copyWith(inLoading: false));
  }

  void _onDetailsDeleteDraftEvent(DetailsDeleteDraftEvent event, emit) async {
    emit(state.copyWith(inLoading: true));
    try {
      await getIt<CreateEventService>().deleteDraft(
        state.event,
      );
      emit(state.copyWith(inLoading: false, needDeepPop: true));
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
        return;
      } else {
        emit(state.copyWith(
            inLoading: false, showErrorPopup: 'Something went wrong'));
      }
    }
    emit(state.copyWith(inLoading: false));

  }

  void _onDetailsPublishEvent(DetailsPublishEvent event, emit) async {
    emit(state.copyWith(inLoading: true));
    try {
      await getIt<CreateEventService>().publishEvent(
        state.event,
      );
      emit(state.copyWith(inLoading: false, needDeepPop: true));
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(inLoading: false, showErrorPopup: e.message));
        return;
      } else {
        print(e);
        emit(state.copyWith(
            inLoading: false, showErrorPopup: 'Something went wrong'));
      }
    }
    emit(state.copyWith(inLoading: false));

  }

  void startPoling() {
    _timer = Timer.periodic(const Duration(seconds: 5), (timer) async {
      if(state.isPreview) return;
      add(RefreshEventDetailsEvent());
    });
  }

  void _onRefreshEventDetailsEvent(
      RefreshEventDetailsEvent event, Emitter<EventDetailsState> emit) async {
    if(state.isPreview) return;
    try {
      await _repository.getEventById(state.event.id).then((event) {
        if (event != null) {
          add(EventDetailsUpdateEvent(event));
        }
      });
    } catch (e) {
      print(e);
    }
  }

  void _onUpdateEntity(EventDetailsUpdateEvent event, emit) {
    if(disabledPolling) return;
    emit(
      state.copyWith(
        inLoading: false,
        event: event.event,
        shifts: event.event.shifts?.map(
              (shift) {
                var oldShift = state.shifts
                    .firstWhereOrNull((e) => e.shift.id == shift.id);
                return ShiftState(
                    isExpanded: oldShift?.isExpanded ?? false,
                    shift: shift,
                    positions: shift.positions.map(
                      (position) {
                        var oldPosition = oldShift?.positions.firstWhereOrNull(
                            (e) => e.position.id == position.id);
                        return PositionState(
                            position: position,
                            isExpanded: oldPosition?.isExpanded ?? false,
                            isStaffExpanded:
                                oldPosition?.isStaffExpanded ?? false);
                      },
                    ).toList());
              },
            ).toList() ??
            [],
      ),
    );
  }

  Future<bool> checkGeofancing(StaffContact staffContact) async {
    var permissionResult =
        await GeofencingService().requestGeolocationPermission();

    if (permissionResult == GeolocationStatus.enabled) {
      var result = await GeofencingService().isInRangeCheck(
        pointLatitude:
            staffContact.parentPosition?.parentShift?.fullAddress?.latitude ??
                0,
        pointLongitude:
            staffContact.parentPosition?.parentShift?.fullAddress?.longitude ??
                0,
      );
      if (result) {
        return true;
      } else {
        add(GeofencingEvent(GeofencingDialogState.tooFar));
      }
    } else if (permissionResult == GeolocationStatus.disabled) {
      add(GeofencingEvent(GeofencingDialogState.locationDisabled));
    } else if (permissionResult == GeolocationStatus.prohibited) {
      add(GeofencingEvent(GeofencingDialogState.goToSettings));
    } else if (permissionResult == GeolocationStatus.denied) {
      add(GeofencingEvent(GeofencingDialogState.permissionDenied));
    }
    return false;
  }

  void _onGeofencingEvent(GeofencingEvent event, emit) {
    emit(state.copyWith(geofencingDialogState: event.dialogState));
    if (event.dialogState != GeofencingDialogState.none) {
      add(GeofencingEvent(GeofencingDialogState.none));
    }
  }

  void _onDisablePollingEvent(DisablePollingEvent event, emit) {
    disabledPolling = true;
    _timer?.cancel();
    _timer = null;
  }

  void _onEnablePollingEvent(EnablePollingEvent event, emit) {
    disabledPolling = false;

    if (_timer == null) {
      startPoling();
    }
  }

  @override
  void onEvent(EventDetailsEvent event) {
      print(event);
    super.onEvent(event);
  }
}
