import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:meta/meta.dart';

import '../../../domain/arguments/get_available_shifts_arguments.dart';
import '../../../domain/arguments/get_my_shifts_arguments.dart';
import '../../../domain/usecases/get_available_shifts_usecase.dart';
import '../../../domain/usecases/get_cancelled_shifts_usecase.dart';
import '../../../domain/usecases/get_history_shifts_usecase.dart';
import '../../../domain/usecases/get_my_shifts_usecase.dart';
import '../../../domain/usecases/get_pending_assignments_usecase.dart';

part 'shifts_event.dart';
part 'shifts_state.dart';

class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
    with BlocErrorHandler<ShiftsState> {
  final GetMyShiftsUseCase getMyShifts;
  final GetAvailableShiftsUseCase getAvailableShifts;
  final GetPendingAssignmentsUseCase getPendingAssignments;
  final GetCancelledShiftsUseCase getCancelledShifts;
  final GetHistoryShiftsUseCase getHistoryShifts;

  ShiftsBloc({
    required this.getMyShifts,
    required this.getAvailableShifts,
    required this.getPendingAssignments,
    required this.getCancelledShifts,
    required this.getHistoryShifts,
  }) : super(ShiftsInitial()) {
    on<LoadShiftsEvent>(_onLoadShifts);
    on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts);
    on<LoadAvailableShiftsEvent>(_onLoadAvailableShifts);
    on<LoadFindFirstEvent>(_onLoadFindFirst);
    on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange);
    on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
  }

  Future<void> _onLoadShifts(
    LoadShiftsEvent event,
    Emitter<ShiftsState> emit,
  ) async {
    if (state is! ShiftsLoaded) {
      emit(ShiftsLoading());
    }

    await handleError(
      emit: emit,
      action: () async {
        final List<DateTime> days = _getCalendarDaysForOffset(0);
        final myShiftsResult = await getMyShifts(
          GetMyShiftsArguments(start: days.first, end: days.last),
        );

        emit(ShiftsLoaded(
          myShifts: myShiftsResult,
          pendingShifts: const [],
          cancelledShifts: const [],
          availableShifts: const [],
          historyShifts: const [],
          availableLoading: false,
          availableLoaded: false,
          historyLoading: false,
          historyLoaded: false,
          myShiftsLoaded: true,
          searchQuery: '',
          jobType: 'all',
        ));
      },
      onError: (String errorKey) => ShiftsError(errorKey),
    );
  }

  Future<void> _onLoadHistoryShifts(
    LoadHistoryShiftsEvent event,
    Emitter<ShiftsState> emit,
  ) async {
    final currentState = state;
    if (currentState is! ShiftsLoaded) return;
    if (currentState.historyLoading || currentState.historyLoaded) return;

    emit(currentState.copyWith(historyLoading: true));
    await handleError(
      emit: emit,
      action: () async {
        final historyResult = await getHistoryShifts();
        emit(currentState.copyWith(
          myShiftsLoaded: true,
          historyShifts: historyResult,
          historyLoading: false,
          historyLoaded: true,
        ));
      },
      onError: (String errorKey) {
        if (state is ShiftsLoaded) {
          return (state as ShiftsLoaded).copyWith(historyLoading: false);
        }
        return ShiftsError(errorKey);
      },
    );
  }

  Future<void> _onLoadAvailableShifts(
    LoadAvailableShiftsEvent event,
    Emitter<ShiftsState> emit,
  ) async {
    final currentState = state;
    if (currentState is! ShiftsLoaded) return;
    if (currentState.availableLoading || currentState.availableLoaded) return;

    emit(currentState.copyWith(availableLoading: true));
    await handleError(
      emit: emit,
      action: () async {
        final availableResult =
            await getAvailableShifts(const GetAvailableShiftsArguments());
        emit(currentState.copyWith(
          availableShifts: _filterPastShifts(availableResult),
          availableLoading: false,
          availableLoaded: true,
        ));
      },
      onError: (String errorKey) {
        if (state is ShiftsLoaded) {
          return (state as ShiftsLoaded).copyWith(availableLoading: false);
        }
        return ShiftsError(errorKey);
      },
    );
  }

  Future<void> _onLoadFindFirst(
    LoadFindFirstEvent event,
    Emitter<ShiftsState> emit,
  ) async {
    if (state is! ShiftsLoaded) {
      emit(const ShiftsLoaded(
        myShifts: [],
        pendingShifts: [],
        cancelledShifts: [],
        availableShifts: [],
        historyShifts: [],
        availableLoading: false,
        availableLoaded: false,
        historyLoading: false,
        historyLoaded: false,
        myShiftsLoaded: false,
        searchQuery: '',
        jobType: 'all',
      ));
    }

    final currentState = state is ShiftsLoaded ? state as ShiftsLoaded : null;
    if (currentState != null && currentState.availableLoaded) return;

    if (currentState != null) {
      emit(currentState.copyWith(availableLoading: true));
    }

    await handleError(
      emit: emit,
      action: () async {
        final availableResult =
            await getAvailableShifts(const GetAvailableShiftsArguments());
        final loadedState = state is ShiftsLoaded
            ? state as ShiftsLoaded
            : const ShiftsLoaded(
                myShifts: [],
                pendingShifts: [],
                cancelledShifts: [],
                availableShifts: [],
                historyShifts: [],
                availableLoading: true,
                availableLoaded: false,
                historyLoading: false,
                historyLoaded: false,
                myShiftsLoaded: false,
                searchQuery: '',
                jobType: 'all',
              );
        emit(loadedState.copyWith(
          availableShifts: _filterPastShifts(availableResult),
          availableLoading: false,
          availableLoaded: true,
        ));
      },
      onError: (String errorKey) {
        if (state is ShiftsLoaded) {
          return (state as ShiftsLoaded).copyWith(availableLoading: false);
        }
        return ShiftsError(errorKey);
      },
    );
  }

  Future<void> _onLoadShiftsForRange(
    LoadShiftsForRangeEvent event,
    Emitter<ShiftsState> emit,
  ) async {
    await handleError(
      emit: emit,
      action: () async {
        final myShiftsResult = await getMyShifts(
          GetMyShiftsArguments(start: event.start, end: event.end),
        );

        if (state is ShiftsLoaded) {
          final currentState = state as ShiftsLoaded;
          emit(currentState.copyWith(
            myShifts: myShiftsResult,
            myShiftsLoaded: true,
          ));
          return;
        }

        emit(ShiftsLoaded(
          myShifts: myShiftsResult,
          pendingShifts: const [],
          cancelledShifts: const [],
          availableShifts: const [],
          historyShifts: const [],
          availableLoading: false,
          availableLoaded: false,
          historyLoading: false,
          historyLoaded: false,
          myShiftsLoaded: true,
          searchQuery: '',
          jobType: 'all',
        ));
      },
      onError: (String errorKey) => ShiftsError(errorKey),
    );
  }

  Future<void> _onFilterAvailableShifts(
    FilterAvailableShiftsEvent event,
    Emitter<ShiftsState> emit,
  ) async {
    final currentState = state;
    if (currentState is ShiftsLoaded) {
      if (!currentState.availableLoaded && !currentState.availableLoading) {
        add(LoadAvailableShiftsEvent());
        return;
      }

      await handleError(
        emit: emit,
        action: () async {
          final result = await getAvailableShifts(GetAvailableShiftsArguments(
            query: event.query ?? currentState.searchQuery,
            type: event.jobType ?? currentState.jobType,
          ));

          emit(currentState.copyWith(
            availableShifts: _filterPastShifts(result),
            searchQuery: event.query ?? currentState.searchQuery,
            jobType: event.jobType ?? currentState.jobType,
          ));
        },
        onError: (String errorKey) {
          // Stay on current state for filtering errors, maybe show a snackbar?
          // For now just logging is enough via handleError mixin.
          return currentState;
        },
      );
    }
  }

  List<DateTime> _getCalendarDaysForOffset(int weekOffset) {
    final now = DateTime.now();
    final int reactDayIndex = now.weekday == 7 ? 0 : now.weekday;
    final int daysSinceFriday = (reactDayIndex + 2) % 7;
    final start = now
        .subtract(Duration(days: daysSinceFriday))
        .add(Duration(days: weekOffset * 7));
    final startDate = DateTime(start.year, start.month, start.day);
    return List.generate(7, (index) => startDate.add(Duration(days: index)));
  }

  List<Shift> _filterPastShifts(List<Shift> shifts) {
    final now = DateTime.now();
    final today = DateTime(now.year, now.month, now.day);
    return shifts.where((shift) {
      if (shift.date.isEmpty) return false;
      try {
        final shiftDate = DateTime.parse(shift.date);
        final dateOnly = DateTime(
          shiftDate.year,
          shiftDate.month,
          shiftDate.day,
        );
        return !dateOnly.isBefore(today);
      } catch (_) {
        return false;
      }
    }).toList();
  }
}
