import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/dto/discount_add_response.dart';
import 'package:hl_lieferservice/dto/discount_update_response.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_state.dart';
import 'package:hl_lieferservice/feature/delivery/detail/repository/delivery_repository.dart';
import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart';

import '../../../../dto/discount_remove_response.dart';
import '../../../../model/article.dart';
import '../../../../model/delivery.dart' as model;

class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
  OperationBloc opBloc;
  DeliveryRepository repository;
  NoteRepository noteRepository;

  DeliveryBloc({
    required this.opBloc,
    required this.repository,
    required this.noteRepository,
  }) : super(DeliveryInitial()) {
    on<UnscanArticleEvent>(_unscan);
    on<ResetScanAmountEvent>(_resetAmount);
    on<LoadDeliveryEvent>(_load);
    on<AddDiscountEvent>(_addDiscount);
    on<RemoveDiscountEvent>(_removeDiscount);
    on<UpdateDiscountEvent>(_updateDiscount);
    on<UpdateDeliveryOptionEvent>(_updateDeliveryOptions);
    on<UpdateSelectedPaymentMethodEvent>(_updatePayment);
    on<FinishDeliveryEvent>(_finishDelivery);
  }

  void _finishDelivery(
    FinishDeliveryEvent event,
    Emitter<DeliveryState> emit,
  ) async {
    final currentState = state;
    opBloc.add(LoadOperation());

    if (currentState is DeliveryLoaded) {
      try {
        model.Delivery newDelivery = event.delivery.copyWith();
        newDelivery.state = model.DeliveryState.finished;

        for (final option in event.delivery.options) {
          debugPrint("VALUE=${option.value};KEY=${option.key}");
        }

        await repository.updateDelivery(newDelivery);
        await noteRepository.addNamedImage(
          event.delivery.id,
          event.driverSignature,
          "delivery_${event.delivery.id}_signature_driver.jpg",
        );
        await noteRepository.addNamedImage(
          event.delivery.id,
          event.customerSignature,
          "delivery_${event.delivery.id}_signature_customer.jpg",
        );
        emit(DeliveryFinished(delivery: newDelivery));
        opBloc.add(FinishOperation());
      } catch (e, st) {
        opBloc.add(FailOperation(message: "Failed to update delivery"));
        debugPrint(st.toString());
      }
    }
  }

  void _updatePayment(
    UpdateSelectedPaymentMethodEvent event,
    Emitter<DeliveryState> emit,
  ) {
    final currentState = state;

    if (currentState is DeliveryLoaded) {
      emit(
        DeliveryLoaded(
          delivery: currentState.delivery.copyWith(payment: event.payment),
        ),
      );
    }
  }

  void _updateDeliveryOptions(
    UpdateDeliveryOptionEvent event,
    Emitter<DeliveryState> emit,
  ) {
    final currentState = state;

    if (currentState is DeliveryLoaded) {
      List<model.DeliveryOption> options =
          currentState.delivery.options.map((option) {
            if (option.key == event.key) {
              if (option.numerical) {
                return option.copyWith(value: event.value);
              } else {
                return option.copyWith(value: event.value == true ? "1" : "0");
              }
            }

            return option;
          }).toList();

      emit(
        DeliveryLoaded(
          delivery: currentState.delivery.copyWith(options: options),
        ),
      );
    }
  }

  void _updateDiscount(
    UpdateDiscountEvent event,
    Emitter<DeliveryState> emit,
  ) async {
    opBloc.add(LoadOperation());

    try {
      final currentState = state;

      if (currentState is DeliveryLoaded) {
        DiscountUpdateResponseDTO response = await repository.updateDiscount(
          event.deliveryId,
          event.reason,
          event.value,
        );

        model.Delivery delivery = currentState.delivery;

        if (response.values?.receipt != null) {
          delivery.totalNetValue = response.values!.receipt.net;
          delivery.totalGrossValue = response.values!.receipt.gross;
        }

        String discountArticleNumber = delivery.discount!.article.articleNumber;
        delivery.discount = model.Discount(
          article:
              response.values?.article != null
                  ? Article.fromDTO(response.values!.article)
                  : delivery.discount!.article,
          note:
              response.values?.note != null
                  ? response.values!.note.noteDescription
                  : delivery.discount!.note,
          noteId:
              response.values?.note != null
                  ? response.values!.note.rowId
                  : delivery.discount!.noteId,
        );

        delivery.articles = [
          ...delivery.articles.where(
            (article) => article.articleNumber != discountArticleNumber,
          ),
          delivery.discount!.article,
        ];

        emit(currentState.copyWith(delivery));

        opBloc.add(FinishOperation());
      }
    } catch (e, st) {
      debugPrint(
        "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
      );
      debugPrint("$e");
      debugPrint("$st");

      opBloc.add(
        FailOperation(message: "Fehler beim Hinzufügen des Discounts: $e"),
      );
    }
  }

  void _removeDiscount(
    RemoveDiscountEvent event,
    Emitter<DeliveryState> emit,
  ) async {
    opBloc.add(LoadOperation());

    try {
      final currentState = state;

      if (currentState is DeliveryLoaded) {
        model.Delivery delivery = currentState.delivery;

        DiscountRemoveResponseDTO response = await repository.removeDiscount(
          event.deliveryId,
        );

        delivery.articles =
            delivery.articles
                .where(
                  (article) =>
                      article.internalId !=
                      delivery.discount?.article.internalId,
                )
                .toList();

        delivery.discount = null;
        delivery.totalGrossValue = response.receipt.gross;
        delivery.totalNetValue = response.receipt.net;

        emit(currentState.copyWith(delivery));

        opBloc.add(FinishOperation());
      }
    } catch (e, st) {
      debugPrint(
        "Fehler beim Löschen des Discounts der Lieferung: ${event.deliveryId}:",
      );
      debugPrint("$e");
      debugPrint("$st");

      opBloc.add(
        FailOperation(message: "Fehler beim Löschen des Discounts: $e"),
      );
    }
  }

  void _addDiscount(AddDiscountEvent event, Emitter<DeliveryState> emit) async {
    opBloc.add(LoadOperation());

    try {
      final currentState = state;

      if (currentState is DeliveryLoaded) {
        DiscountAddResponseDTO response = await repository.addDiscount(
          event.deliveryId,
          event.reason,
          event.value,
        );

        model.Delivery delivery = currentState.delivery;
        delivery.totalNetValue = response.values.receipt.net;
        delivery.totalGrossValue = response.values.receipt.gross;

        delivery.discount = model.Discount(
          article: Article.fromDTO(response.values.article),
          note: response.values.note.noteDescription,
          noteId: response.values.note.rowId,
        );

        delivery.articles = [...delivery.articles, delivery.discount!.article];

        emit(currentState.copyWith(delivery));

        opBloc.add(FinishOperation());
      }
    } catch (e, st) {
      debugPrint(
        "Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
      );
      debugPrint("$e");
      debugPrint("$st");

      opBloc.add(
        FailOperation(message: "Fehler beim Hinzufügen des Discounts: $e"),
      );
    }
  }

  void _load(LoadDeliveryEvent event, Emitter<DeliveryState> emit) async {
    debugPrint("Discount; ${event.delivery.discount?.note}");
    emit(DeliveryLoaded(delivery: event.delivery));
  }

  void _unscan(UnscanArticleEvent event, Emitter<DeliveryState> emit) async {
    opBloc.add(LoadOperation());

    try {
      String? noteId = await repository.unscan(
        event.articleId,
        event.newAmount,
        event.reason,
      );

      if (noteId != null) {
        final currentState = state;

        if (currentState is DeliveryLoaded) {
          Article article = currentState.delivery.articles.firstWhere(
            (article) => article.internalId == int.parse(event.articleId),
          );

          article.removeNoteId = noteId;
          article.scannedRemovedAmount += event.newAmount;
          article.scannedAmount -= event.newAmount;

          List<Article> articles = [
            ...currentState.delivery.articles.where(
              (article) => article.internalId != int.parse(event.articleId),
            ),
            article,
          ];
          currentState.delivery.articles = articles;

          emit.call(currentState.copyWith(currentState.delivery));
        }
      }

      opBloc.add(FinishOperation());
    } catch (e, st) {
      debugPrint("Fehler beim Unscan des Artikels: ${event.articleId}:");
      debugPrint("$e");
      debugPrint("$st");

      opBloc.add(FailOperation(message: "Fehler beim Unscan des Artikels: $e"));
    }
  }

  void _resetAmount(
    ResetScanAmountEvent event,
    Emitter<DeliveryState> emit,
  ) async {
    opBloc.add(LoadOperation());

    try {
      final currentState = state;
      await repository.resetScan(event.articleId);

      if (currentState is DeliveryLoaded) {
        Article article = currentState.delivery.articles.firstWhere(
          (article) => article.internalId == int.parse(event.articleId),
        );

        article.removeNoteId = null;
        article.scannedRemovedAmount = 0;
        article.scannedAmount = article.amount;

        List<Article> articles = [
          ...currentState.delivery.articles.where(
            (article) => article.internalId != int.parse(event.articleId),
          ),
          article,
        ];
        currentState.delivery.articles = articles;

        emit.call(currentState.copyWith(currentState.delivery));
      }

      opBloc.add(FinishOperation());
    } catch (e, st) {
      debugPrint("Fehler beim Unscan des Artikels: ${event.articleId}:");
      debugPrint("$e");
      debugPrint("$st");

      opBloc.add(FailOperation(message: "Fehler beim Zurücksetzen: $e"));
    }
  }
}
