import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:geolocator/geolocator.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/usecases/get_todays_shift_usecase.dart';
import '../../domain/usecases/get_attendance_status_usecase.dart';
import '../../domain/usecases/clock_in_usecase.dart';
import '../../domain/usecases/clock_out_usecase.dart';
import '../../domain/arguments/clock_in_arguments.dart';
import '../../domain/arguments/clock_out_arguments.dart';
import 'clock_in_event.dart';
import 'clock_in_state.dart';

class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
    with BlocErrorHandler<ClockInState> {
  ClockInBloc({
    required GetTodaysShiftUseCase getTodaysShift,
    required GetAttendanceStatusUseCase getAttendanceStatus,
    required ClockInUseCase clockIn,
    required ClockOutUseCase clockOut,
  })  : _getTodaysShift = getTodaysShift,
        _getAttendanceStatus = getAttendanceStatus,
        _clockIn = clockIn,
        _clockOut = clockOut,
        super(ClockInState(selectedDate: DateTime.now())) {
    on<ClockInPageLoaded>(_onLoaded);
    on<ShiftSelected>(_onShiftSelected);
    on<DateSelected>(_onDateSelected);
    on<CheckInRequested>(_onCheckIn);
    on<CheckOutRequested>(_onCheckOut);
    on<CheckInModeChanged>(_onModeChanged);
    on<RequestLocationPermission>(_onRequestLocationPermission);
    on<CommuteModeToggled>(_onCommuteModeToggled);
    on<LocationUpdated>(_onLocationUpdated);

    add(ClockInPageLoaded());
  }
  final GetTodaysShiftUseCase _getTodaysShift;
  final GetAttendanceStatusUseCase _getAttendanceStatus;
  final ClockInUseCase _clockIn;
  final ClockOutUseCase _clockOut;

  // Mock Venue Location (e.g., Grand Hotel, NYC)
  static const double allowedRadiusMeters = 500;

  Future<void> _onLoaded(
    ClockInPageLoaded event,
    Emitter<ClockInState> emit,
  ) async {
    emit(state.copyWith(status: ClockInStatus.loading));
    await handleError(
      emit: emit,
      action: () async {
        final List<Shift> shifts = await _getTodaysShift();
        final AttendanceStatus status = await _getAttendanceStatus();

        Shift? selectedShift;
        if (shifts.isNotEmpty) {
          if (status.activeShiftId != null) {
            try {
              selectedShift =
                  shifts.firstWhere((Shift s) => s.id == status.activeShiftId);
            } catch (_) {}
          }
          selectedShift ??= shifts.last;
        }

        emit(state.copyWith(
          status: ClockInStatus.success,
          todayShifts: shifts,
          selectedShift: selectedShift,
          attendance: status,
        ));

        if (selectedShift != null && !status.isCheckedIn) {
          add(RequestLocationPermission());
        }
      },
      onError: (String errorKey) => state.copyWith(
        status: ClockInStatus.failure,
        errorMessage: errorKey,
      ),
    );
  }

  Future<void> _onRequestLocationPermission(
    RequestLocationPermission event,
    Emitter<ClockInState> emit,
  ) async {
    await handleError(
      emit: emit,
      action: () async {
        LocationPermission permission = await Geolocator.checkPermission();
        if (permission == LocationPermission.denied) {
          permission = await Geolocator.requestPermission();
        }

        final bool hasConsent =
            permission == LocationPermission.always ||
            permission == LocationPermission.whileInUse;

        emit(state.copyWith(hasLocationConsent: hasConsent));

        if (hasConsent) {
          await _startLocationUpdates();
        }
      },
      onError: (String errorKey) => state.copyWith(
        errorMessage: errorKey,
      ),
    );
  }

  Future<void> _startLocationUpdates() async {
    // Note: handleErrorWithResult could be used here too if we want centralized logging/conversion
    try {
      final Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
      );

      double distance = 0;
      bool isVerified =
          false; // Require location match by default if shift has location

      if (state.selectedShift != null &&
          state.selectedShift!.latitude != null &&
          state.selectedShift!.longitude != null) {
        distance = Geolocator.distanceBetween(
          position.latitude,
          position.longitude,
          state.selectedShift!.latitude!,
          state.selectedShift!.longitude!,
        );
        isVerified = distance <= allowedRadiusMeters;
      } else {
        isVerified = true;
      }

      if (!isClosed) {
        add(
          LocationUpdated(
            position: position,
            distance: distance,
            isVerified: isVerified,
          ),
        );
      }
    } catch (_) {
      // Geolocator errors usually handled via onRequestLocationPermission
    }
  }

  void _onLocationUpdated(
    LocationUpdated event,
    Emitter<ClockInState> emit,
  ) {
    emit(state.copyWith(
      currentLocation: event.position,
      distanceFromVenue: event.distance,
      isLocationVerified: event.isVerified,
      etaMinutes:
          (event.distance / 80).round(), // Rough estimate: 80m/min walking speed
    ));
  }

  void _onCommuteModeToggled(
    CommuteModeToggled event,
    Emitter<ClockInState> emit,
  ) {
    emit(state.copyWith(isCommuteModeOn: event.isEnabled));
    if (event.isEnabled) {
      add(RequestLocationPermission());
    }
  }

  void _onShiftSelected(
    ShiftSelected event,
    Emitter<ClockInState> emit,
  ) {
    emit(state.copyWith(selectedShift: event.shift));
    if (!state.attendance.isCheckedIn) {
      _startLocationUpdates();
    }
  }

  void _onDateSelected(
    DateSelected event,
    Emitter<ClockInState> emit,
  ) {
    emit(state.copyWith(selectedDate: event.date));
  }

  void _onModeChanged(
    CheckInModeChanged event,
    Emitter<ClockInState> emit,
  ) {
    emit(state.copyWith(checkInMode: event.mode));
  }

  Future<void> _onCheckIn(
    CheckInRequested event,
    Emitter<ClockInState> emit,
  ) async {
    emit(state.copyWith(status: ClockInStatus.actionInProgress));
    await handleError(
      emit: emit,
      action: () async {
        final AttendanceStatus newStatus = await _clockIn(
          ClockInArguments(shiftId: event.shiftId, notes: event.notes),
        );
        emit(state.copyWith(
          status: ClockInStatus.success,
          attendance: newStatus,
        ));
      },
      onError: (String errorKey) => state.copyWith(
        status: ClockInStatus.failure,
        errorMessage: errorKey,
      ),
    );
  }

  Future<void> _onCheckOut(
    CheckOutRequested event,
    Emitter<ClockInState> emit,
  ) async {
    emit(state.copyWith(status: ClockInStatus.actionInProgress));
    await handleError(
      emit: emit,
      action: () async {
        final AttendanceStatus newStatus = await _clockOut(
          ClockOutArguments(
            notes: event.notes,
            breakTimeMinutes: 0,
            applicationId: state.attendance.activeApplicationId,
          ),
        );
        emit(state.copyWith(
          status: ClockInStatus.success,
          attendance: newStatus,
        ));
      },
      onError: (String errorKey) => state.copyWith(
        status: ClockInStatus.failure,
        errorMessage: errorKey,
      ),
    );
  }
}
