import 'dart:developer';

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/common/date_time_extension.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/data/enums/state_status.dart';
import 'package:krow/features/profile/schedule/domain/entities/day_shedule.dart';
import 'package:krow/features/profile/schedule/domain/entities/schedule_slot.dart';
import 'package:krow/features/profile/schedule/domain/staff_schedule_repository.dart';

part 'schedule_event.dart';

part 'schedule_state.dart';

class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
  ScheduleBloc() : super(const ScheduleState()) {
    on<ScheduleInitEvent>(_onBlocInit);
    on<ScheduleEventChangeMode>(_onChangeMode);
    on<ScheduleEventSelectDates>(_onSelectDates);
    on<ScheduleEventAddSlot>(_onAddSlot);
    on<ScheduleEventRemoveSlot>(_onRemoveSlot);
    on<ScheduleEventEditSlot>(_onEditSlot);
    on<ScheduleEventSave>(_onConfirmChanges);
    on<ScheduleEventCancel>(_onCancelChanges);
    on<ScheduleEventDeleteSchedule>(_onDeleteSchedule);
  }

  final StaffScheduleRepository staffRepository =
      getIt<StaffScheduleRepository>();

  Future<void> _onBlocInit(
    ScheduleInitEvent event,
    Emitter<ScheduleState> emit,
  ) async {
    emit(state.copyWith(status: StateStatus.loading));

    try {
      await for (final scheduleData in staffRepository.getStaffSchedule()) {
        emit(
          state.copyWith(
            schedules: scheduleData,
            status: StateStatus.idle,
          ),
        );
      }
    } catch (except) {
      log(except.toString());
    }

    if (state.status == StateStatus.loading) {
      emit(state.copyWith(status: StateStatus.idle));
    }
  }

  Future<void> _onSelectDates(
    ScheduleEventSelectDates event,
    Emitter<ScheduleState> emit,
  ) async {
    if (event.dates.length == 1) {
      emit(
        state.copyWith(
          scheduleErrorText: '',
          selectedDates: [event.dates.first],
          prevSelectedDates: [],
          isInEditMode: false,
        ),
      );

      emit(
        state.copyWith(
          selectedSchedules: await state.findSchedulesForSelection(),
        ),
      );
      return;
    }

    var startDate = event.dates.first;
    final endDate = event.dates.last;

    emit(
      state.copyWith(
        scheduleErrorText: '',
        selectedDates: [
          for (var i = 0; i < endDate.difference(startDate).inDays; i++)
            startDate.add(Duration(days: i)),
          endDate,
        ],
        prevSelectedDates: [],
        isInEditMode: false,
      ),
    );

    emit(
      state.copyWith(
        selectedSchedules: await state.findSchedulesForSelection(),
      ),
    );
  }

  void _onChangeMode(
    ScheduleEventChangeMode event,
    Emitter<ScheduleState> emit,
  ) {
    if (event.isInScheduleAddMode != null && state.selectedDates.length == 1) {
      emit(state.copyWith(
        scheduleErrorText: '',
        isScheduleAddMode: event.isInScheduleAddMode,
      ));
      return;
    }

    if (event.editedDate != null) {
      emit(
        state.copyWith(
          selectedDates: [event.editedDate!],
          prevSelectedDates: List.from(state.selectedDates),
        ),
      );
    }

    emit(
      state.copyWith(
        isInEditMode: event.isInEditScheduleMode,
        scheduleErrorText: '',
        isScheduleAddMode: false,
        tempSchedules: state.selectedDates.length == 1
            ? [
                state.getScheduleForDate(
                  date: state.selectedDates.first,
                  isWeekly: event.isWeekly,
                ),
              ]
            : [
                for (int i = 0; i < state.selectedDates.length; i++)
                  DaySchedule(
                    date: state.selectedDates[i],
                    slots: const [],
                  ),
              ],
      ),
    );
  }

  void _onAddSlot(
    ScheduleEventAddSlot event,
    Emitter<ScheduleState> emit,
  ) {
    emit(
      state.copyWith(
        tempSchedules: state.tempSchedules.map(
          (daySchedule) {
            final startTime = daySchedule.getLatestSlot();

            return daySchedule.copyWith(
              slots: [
                ...daySchedule.slots,
                ScheduleSlot.minFromStartTime(
                  start: startTime,
                ),
              ],
            );
          },
        ).toList(),
      ),
    );
  }

  void _onRemoveSlot(
    ScheduleEventRemoveSlot event,
    Emitter<ScheduleState> emit,
  ) {
    emit(
      state.copyWith(
        scheduleErrorText: '',
        tempSchedules: state.tempSchedules.map(
          (daySchedule) {
            return daySchedule.removeSlotAtIndex(event.slotIndex);
          },
        ).toList(),
      ),
    );
  }

  void _onEditSlot(
    ScheduleEventEditSlot event,
    Emitter<ScheduleState> emit,
  ) {
    emit(
      state.copyWith(
        scheduleErrorText: '',
        tempSchedules: state.tempSchedules.map(
          (daySchedule) {
            final daySlots = List<ScheduleSlot>.from(daySchedule.slots);
            daySlots[event.slotIndex] = daySlots[event.slotIndex].editTime(
              startTime: event.start?.copyWith(
                day: daySchedule.date.day,
                month: daySchedule.date.month,
              ),
              endTime: event.end?.copyWith(
                day: daySchedule.date.day,
                month: daySchedule.date.month,
              ),
            );

            return daySchedule.copyWith(
              slots: daySlots,
            );
          },
        ).toList(),
      ),
    );
  }

  Future<Map<String, DaySchedule>> _handleTemporaryScheduleSave() async {
    final previousSchedules = state.getDailySchedulesForTemporary();
    if (state.tempSchedules.length == 1) {
      return previousSchedules.isNotEmpty
          ? staffRepository.updateStaffSchedule(
              schedules: state.tempSchedules,
            )
          : staffRepository.createStaffSchedule(
              schedules: state.tempSchedules,
            );
    }

    if (previousSchedules.isNotEmpty) {
      await staffRepository.deleteStaffSchedule(schedules: previousSchedules);
    }

    return staffRepository.createStaffSchedule(
      schedules: state.tempSchedules,
    );
  }

  Future<void> _onConfirmChanges(
    ScheduleEventSave event,
    Emitter<ScheduleState> emit,
  ) async {
    emit(
      state.copyWith(
        status: StateStatus.loading,
        selectedDates:
            state.prevSelectedDates.isNotEmpty ? state.prevSelectedDates : null,
        prevSelectedDates: [],
      ),
    );

    final isTempScheduleValid = await state.isValid;
    if (!isTempScheduleValid) {
      emit(
        state.copyWith(
          status: StateStatus.error,
          scheduleErrorText: 'overlap_error'.tr(),
        ),
      );
      return;
    }

    Map<String, DaySchedule>? schedules;
    try {
      schedules = await _handleTemporaryScheduleSave();
    } catch (except) {
      log(except.toString());
    }

    emit(
      state.copyWith(
        status: StateStatus.idle,
        isInEditMode: false,
        schedules: schedules,
        tempSchedules: [],
      ),
    );

    emit(
      state.copyWith(
        selectedSchedules: await state.findSchedulesForSelection(),
      ),
    );
  }

  void _onCancelChanges(
    ScheduleEventCancel event,
    Emitter<ScheduleState> emit,
  ) {
    emit(
      state.copyWith(
        isInEditMode: false,
        scheduleErrorText: '',
        tempSchedules: [],
        selectedDates:
            state.prevSelectedDates.isNotEmpty ? state.prevSelectedDates : null,
        prevSelectedDates: [],
      ),
    );
  }

  Future<void> _onDeleteSchedule(
    ScheduleEventDeleteSchedule event,
    Emitter<ScheduleState> emit,
  ) async {
    emit(
      state.copyWith(
        isInEditMode: false,
        status: StateStatus.loading,
      ),
    );

    Map<String, DaySchedule>? schedules;
    try {
      final result = staffRepository.deleteStaffSchedule(
        schedules: [event.schedule],
      );
      schedules = Map<String, DaySchedule>.from(state.schedules);

      await result;
      schedules.remove(event.schedule.getIdKey());
    } catch (except) {
      log(except.toString());
    }

    emit(
      state.copyWith(
        status: StateStatus.idle,
        schedules: schedules,
      ),
    );

    emit(
      state.copyWith(
        selectedSchedules: await state.findSchedulesForSelection(),
      ),
    );
  }
}
