import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/clients/api/api_exception.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/sevices/geofencing_serivce.dart';
import 'package:krow/features/shifts/data/models/staff_shift.dart';
import 'package:krow/features/shifts/domain/services/force_clockout_service.dart';
import 'package:krow/features/shifts/domain/shift_entity.dart';
import 'package:krow/features/shifts/domain/shifts_repository.dart';

part 'shift_details_event.dart';
part 'shift_details_state.dart';

class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
  ShiftDetailsBloc()
      : super(ShiftDetailsState(shiftViewModel: ShiftEntity.empty)) {
    on<ShiftDetailsInitialEvent>(_onInitial);
    on<StaffGeofencingUpdate>(_onStaffGeofencingUpdate);
    on<ShiftUpdateTimerEvent>(_onUpdateTimer);
    on<ShiftConfirmEvent>(_onConfirm);
    on<ShiftClockInEvent>(_onClockIn);
    on<ShiftCompleteEvent>(_onComplete);
    on<ShiftDeclineEvent>(_onDecline);
    on<ShiftCancelEvent>(_onCancel);
    on<ShiftRefreshEvent>(_onRefresh);
    on<ShiftCheckGeocodingEvent>(_onCheckGeocoding);
    on<ShiftErrorWasShownEvent>(_onErrorWasShown);
  }

  final GeofencingService _geofencingService = getIt<GeofencingService>();
  Timer? _timer;
  Timer? _refreshTimer;
  StreamSubscription<bool>? _geofencingStream;

  Future<void> _onInitial(
    ShiftDetailsInitialEvent event,
    Emitter<ShiftDetailsState> emit,
  ) async {
    emit(state.copyWith(shiftViewModel: event.shift));
    if (event.shift.status == EventShiftRoleStaffStatus.ongoing) {
      _startOngoingTimer();
    }
    _runRefreshTimer();

    add(const ShiftCheckGeocodingEvent());
  }

  void _onStaffGeofencingUpdate(
    StaffGeofencingUpdate event,
    Emitter<ShiftDetailsState> emit,
  ) {
    emit(state.copyWith(isToFar: !event.isInRange));
  }

  void _onUpdateTimer(
    ShiftUpdateTimerEvent event,
    Emitter<ShiftDetailsState> emit,
  ) {
    emit(state.copyWith(shiftViewModel: state.shiftViewModel.copyWith()));
  }

  void _onConfirm(
    ShiftConfirmEvent event,
    Emitter<ShiftDetailsState> emit,
  ) async {
    try {
      emit(state.copyWith(isLoading: true));
      await getIt<ShiftsRepository>().confirmShift(state.shiftViewModel);

      emit(
        state.copyWith(
          shiftViewModel: state.shiftViewModel.copyWith(
            status: EventShiftRoleStaffStatus.confirmed,
          ),
        ),
      );
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(errorMessage: e.message));
      }else{
        emit(state.copyWith(errorMessage: e.toString()));
      }
    }
    emit(state.copyWith(isLoading: false));

  }

  void _onClockIn(
      ShiftClockInEvent event, Emitter<ShiftDetailsState> emit) async {
    emit(state.copyWith(isLoading: true));

    try {
      await getIt<ShiftsRepository>().clockInShift(state.shiftViewModel);
      _geofencingStream?.cancel();
      emit(
        state.copyWith(
          isLoading: false,
          shiftViewModel: state.shiftViewModel.copyWith(
            status: EventShiftRoleStaffStatus.ongoing,
            clockIn: DateTime.now(),
          ),
        ),
      );
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(errorMessage: e.message));
      }else{
        emit(state.copyWith(errorMessage: e.toString()));
      }
      emit(state.copyWith(isLoading: false));
    }
    _startOngoingTimer();

    getIt<ForceClockoutService>().startTrackOngoingLocation(
      state.shiftViewModel,
      () {
        onForceUpdateUI();
      },
    );
  }

  void _onComplete(
    ShiftCompleteEvent event,
    Emitter<ShiftDetailsState> emit,
  ) async {
    emit(state.copyWith(isLoading: true));
    try {
      emit(
        state.copyWith(
          shiftViewModel: state.shiftViewModel.copyWith(
            status: EventShiftRoleStaffStatus.completed,
          ),
        ),
      );
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(errorMessage: e.message));
      }else{
        emit(state.copyWith(errorMessage: e.toString()));
      }
    }
    emit(state.copyWith(isLoading: false));

  }

  void _onDecline(
    ShiftDeclineEvent event,
    Emitter<ShiftDetailsState> emit,
  ) async {
    emit(state.copyWith(isLoading: true));
    try {
      await getIt<ShiftsRepository>().declineShift(
          state.shiftViewModel, event.reason, event.additionalReason);
      emit(state.copyWith(
        needPop: true,
          shiftViewModel: state.shiftViewModel.copyWith(
        status: EventShiftRoleStaffStatus.canceledByStaff,
      )));
    } catch (e) {
      if (e is DisplayableException) {
        emit(state.copyWith(errorMessage: e.message));
      }else{
        emit(state.copyWith(errorMessage: e.toString()));
      }
    }
    emit(state.copyWith(isLoading: false));

  }

  void _onErrorWasShown(
    ShiftErrorWasShownEvent event,
    Emitter<ShiftDetailsState> emit,
  ) {
    emit(state.copyWith(errorMessage: ''));
  }

  void _onCancel(
    ShiftCancelEvent event,
    Emitter<ShiftDetailsState> emit,
  ) async {
    emit(state.copyWith(isLoading: true));
    try {
      await getIt<ShiftsRepository>().cancelShift(
          state.shiftViewModel, event.reason, event.additionalReason);
      emit(state.copyWith(
          shiftViewModel: state.shiftViewModel.copyWith(
        status: EventShiftRoleStaffStatus.canceledByStaff,
      )));
    } catch (e) {
      print('!!!!!! ${e}');
      if (e is DisplayableException) {
        emit(state.copyWith(errorMessage: e.message));
      }else{
        emit(state.copyWith(errorMessage: e.toString()));
      }
    }
    emit(state.copyWith(isLoading: false));

  }

  void _onRefresh(
    ShiftRefreshEvent event,
    Emitter<ShiftDetailsState> emit,
  ) {
    emit(state.copyWith(
        shiftViewModel: event.shift,
        proximityState: GeofencingProximityState.none));
  }

  void _onCheckGeocoding(
    ShiftCheckGeocodingEvent event,
    Emitter<ShiftDetailsState> emit,
  ) async {
    emit(state.copyWith(
      proximityState: GeofencingProximityState.none,
    ));

    await _checkByGeocoding(emit);
  }

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

  void _runRefreshTimer() {
    _refreshTimer = Timer.periodic(
      const Duration(seconds: 10),
      (timer) async {
        final shift = await getIt<ShiftsRepository>()
            .getShiftById(state.shiftViewModel.id);
        if (shift == null) {
          return;
        }
        add((ShiftRefreshEvent(shift)));
      },
    );
  }

  Future<void> _checkByGeocoding(Emitter<ShiftDetailsState> emit) async {
    if (![
      EventShiftRoleStaffStatus.assigned,
      EventShiftRoleStaffStatus.confirmed,
      EventShiftRoleStaffStatus.ongoing
    ].contains(state.shiftViewModel.status)) {
      return;
    }

    final geolocationCheck =
        await _geofencingService.requestGeolocationPermission();
    emit(
      state.copyWith(
        proximityState: switch (geolocationCheck) {
          GeolocationStatus.disabled =>
            GeofencingProximityState.locationDisabled,
          GeolocationStatus.denied => GeofencingProximityState.permissionDenied,
          GeolocationStatus.prohibited => GeofencingProximityState.goToSettings,
          GeolocationStatus.onlyInUse => GeofencingProximityState.onlyInUse,
          GeolocationStatus.enabled => null,
        },
      ),
    );

    if (geolocationCheck == GeolocationStatus.enabled) {
      _geofencingStream = _geofencingService
          .isInRangeStream(
        pointLatitude: state.shiftViewModel.locationLat,
        pointLongitude: state.shiftViewModel.locationLon,
      )
          .listen(
        (isInRange) {

          add(StaffGeofencingUpdate(isInRange: isInRange));
        },
      );
    }
  }

  void _startOngoingTimer() {
    _timer?.cancel();
    _timer = Timer.periodic(
      const Duration(seconds: 10),
      (timer) {
        if (state.shiftViewModel.status != EventShiftRoleStaffStatus.ongoing) {
          timer.cancel();
        } else {
          add(const ShiftUpdateTimerEvent());
        }
      },
    );
  }

  Future<void> onForceUpdateUI() async {
    _timer?.cancel();
    if (!isClosed) {
      final shift =
          await getIt<ShiftsRepository>().getShiftById(state.shiftViewModel.id);
      if (shift == null) {
        return;
      }
      add((ShiftRefreshEvent(shift)));
    }
  }
}
