import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_event.dart';
import 'package:hl_lieferservice/feature/delivery/overview/bloc/tour_state.dart';
import 'package:hl_lieferservice/feature/delivery/overview/repository/tour_repository.dart';
import 'package:hl_lieferservice/feature/delivery/overview/service/distance_service.dart';
import 'package:hl_lieferservice/model/delivery.dart';
import 'package:hl_lieferservice/model/tour.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';

class TourBloc extends Bloc<TourEvent, TourState> {
  OperationBloc opBloc;
  TourRepository tourRepository;

  TourBloc({required this.opBloc, required this.tourRepository})
    : super(TourInitial()) {
    on<LoadTour>(_load);
    on<UpdateTour>(_update);
    on<AssignCarEvent>(_assignCar);
    on<IncrementArticleScanAmount>(_increment);
    on<ScanArticleEvent>(_scan);
    on<HoldDeliveryEvent>(_holdDelivery);
    on<CancelDeliveryEvent>(_cancelDelivery);
    on<ReactivateDeliveryEvent>(_reactiveateDelivery);
  }

  void _reactiveateDelivery(
      ReactivateDeliveryEvent event,
      Emitter<TourState> emit,
      ) async {
    final currentState = state;
    if (currentState is TourLoaded) {
      opBloc.add(LoadOperation());
      try {
        Tour tourCopied = currentState.tour.copyWith();
        Delivery delivery = tourCopied.deliveries.firstWhere((delivery) => delivery.id == event.deliveryId);
        delivery.state = DeliveryState.ongoing;

        await tourRepository.updateDelivery(
          delivery,
        );

        opBloc.add(FinishOperation());

        emit(TourLoaded(tour: tourCopied, distances: currentState.distances));
      } catch (e, st) {
        debugPrint("$e");
        debugPrint("$st");
        opBloc.add(
          FailOperation(message: "Fehler beim Zurückstellen der Lieferung"),
        );
      }
    }
  }

  void _holdDelivery(
      HoldDeliveryEvent event,
      Emitter<TourState> emit,
      ) async {
    final currentState = state;
    if (currentState is TourLoaded) {
      opBloc.add(LoadOperation());
      try {
        Tour tourCopied = currentState.tour.copyWith();
        Delivery delivery = tourCopied.deliveries.firstWhere((delivery) => delivery.id == event.deliveryId);
        delivery.state = DeliveryState.onhold;

        await tourRepository.updateDelivery(
          delivery,
        );

        opBloc.add(FinishOperation());

        emit(TourLoaded(tour: tourCopied, distances: currentState.distances));
      } catch (e, st) {
        debugPrint("$e");
        debugPrint("$st");
        opBloc.add(
          FailOperation(message: "Fehler beim Zurückstellen der Lieferung"),
        );
      }
    }
  }

  void _cancelDelivery(CancelDeliveryEvent event, Emitter<TourState> emit) async {
    final currentState = state;
    if (currentState is TourLoaded) {
      opBloc.add(LoadOperation());
      try {
        Tour tourCopied = currentState.tour.copyWith();
        Delivery delivery = tourCopied.deliveries.firstWhere((delivery) => delivery.id == event.deliveryId);
        delivery.state = DeliveryState.canceled;

        await tourRepository.updateDelivery(
          delivery,
        );

        opBloc.add(FinishOperation());

        emit(TourLoaded(tour: tourCopied, distances: currentState.distances));
      } catch (e, st) {
        debugPrint("$e");
        debugPrint("$st");
        opBloc.add(
          FailOperation(message: "Fehler beim Zurückstellen der Lieferung"),
        );
      }
    }
  }

  void _scan(ScanArticleEvent event, Emitter<TourState> emit) async {
    final currentState = state;
    opBloc.add(LoadOperation());

    if (currentState is TourLoaded) {
      try {
        if (currentState.tour.deliveries.any(
          (delivery) => delivery.articles.any(
            (article) => article.articleNumber == event.articleNumber,
          ),
        )) {
          var tourCopied = currentState.tour.copyWith();
          var delivery = tourCopied.deliveries.firstWhere(
            (delivery) => delivery.id == event.deliveryId,
          );
          var article = delivery.articles.firstWhere(
            (article) => article.articleNumber == event.articleNumber,
          );

          await tourRepository.scanArticle(article.internalId.toString());

          if (article.scannedAmount < article.amount) {
            article.scannedAmount += 1;

            emit(TourLoaded(tour: tourCopied, distances: currentState.distances));
            opBloc.add(FinishOperation(message: '${article.name} gescannt'));
          } else {
            opBloc.add(
              FailOperation(
                message: 'Alle ${article.name} wurden bereits gescannt',
              ),
            );
          }
        } else {
          opBloc.add(
            FailOperation(
              message: 'Fehler: Artikel ist für keine Lieferung vorgesehen',
            ),
          );
        }
      } catch (e, st) {
        debugPrint(st.toString());
        opBloc.add(FailOperation(message: "Fehler beim Scannnen des Artikels"));
      }
    }
  }

  Future<void> _update(UpdateTour event, Emitter<TourState> emit) async {
    final currentState = state;
    if (currentState is TourLoaded) {
      emit(TourLoaded(tour: event.tour, distances: currentState.distances));
    }
  }

  Future<void> _increment(
    IncrementArticleScanAmount event,
    Emitter<TourState> emit,
  ) async {
    final currentState = state;

    if (currentState is TourLoaded) {
      var deliveryCopied = currentState.tour.deliveries.firstWhere(
        (delivery) => delivery.id == event.deliveryId,
      );
      var articleCopied = deliveryCopied.articles.firstWhere(
        (article) => article.internalId == int.parse(event.internalArticleId),
      );
      articleCopied.scannedAmount += 1;

      emit(
        TourLoaded(
          tour: currentState.tour.copyWith(
            deliveries:
                currentState.tour.deliveries.map((delivery) {
                  if (delivery.id == event.deliveryId) {
                    return deliveryCopied;
                  }

                  return delivery;
                }).toList(),
          ),
          distances: currentState.distances
        ),
      );
    }
  }

  Future<void> _assignCar(AssignCarEvent event, Emitter<TourState> emit) async {
    final currentState = state;

    if (currentState is TourLoaded) {
      opBloc.add(LoadOperation());
      var copiedTour = currentState.tour.copyWith();
      var delivery = copiedTour.deliveries.firstWhere(
        (delivery) => delivery.id == event.deliveryId,
      );

      try {
        await tourRepository.assignCar(event.deliveryId, event.carId);
        delivery.carId = int.parse(event.carId);

        emit(
          TourLoaded(
            tour: copiedTour.copyWith(
              deliveries:
                  copiedTour.deliveries.map((d) {
                    if (d.id == delivery.id) {
                      return delivery;
                    }

                    return d;
                  }).toList(),
            ),
            distances: currentState.distances
          ),
        );

        opBloc.add(FinishOperation());
      } catch (e, st) {
        debugPrint(st.toString());
        opBloc.add(
          FailOperation(message: "Fehler beim Zuweisen des Fahrzeugs"),
        );
      }
    }
  }

  Future<void> _load(LoadTour event, Emitter<TourState> emit) async {
    opBloc.add(LoadOperation());
    try {
      Tour tour = await tourRepository.loadAll(event.teamId);
      List<Payment> payments = await tourRepository.loadPaymentOptions();
      tour.paymentMethods = payments;
      Map<String, double> distances = {};

      for (final delivery in tour.deliveries) {
        distances[delivery.id] = await DistanceService.getDistanceByRoad(delivery.customer.address.toString());
      }

      emit(TourLoaded(tour: tour, distances: distances));
      opBloc.add(FinishOperation());
    } catch (e) {
      opBloc.add(
        FailOperation(message: "Fehler beim Laden der heutigen Fahrten"),
      );
    }
  }
}
