import 'dart:typed_data';

import 'package:hl_lieferservice/dto/contact_person.dart';
import 'package:hl_lieferservice/dto/delivery.dart';
import 'package:hl_lieferservice/dto/image_note_response.dart';
import 'package:hl_lieferservice/dto/note.dart';
import 'package:hl_lieferservice/dto/note_template.dart';
import 'package:hl_lieferservice/model/tour.dart';
import 'package:hl_lieferservice/util.dart';
import 'article.dart';
import 'customer.dart';

class PaymentOptions {
  PaymentOptions({this.value = 0.0, this.selectedPaymentMethod = "none"});

  double value;
  String selectedPaymentMethod;
}

class Note {
  Note({required this.content, required this.id});

  final int id;
  String content;

  factory Note.fromDto(NoteDTO dto) {
    return Note(content: dto.note, id: int.parse(dto.id));
  }
}

class NoteTemplate {
  NoteTemplate({
    required this.title,
    required this.content,
    required this.language,
  });

  String title;
  String content;
  String language;

  factory NoteTemplate.fromDTO(NoteTemplateDTO dto) {
    return NoteTemplate(
      title: dto.title,
      content: dto.note,
      language: dto.language,
    );
  }
}

class ContactPerson {
  const ContactPerson({required this.name, required this.phoneNumber});

  final String name;
  final String? phoneNumber;

  factory ContactPerson.fromDTO(ContactPersonDTO dto) {
    String phone;
    String phoneRegex = r'([0-9]+|[0-9]+(-|\/))[0-9]+';

    if (dto.phoneNo == "" && dto.mobileNo != "") {
      phone = dto.mobileNo;
    } else if (dto.phoneNo != "" && dto.mobileNo == "") {
      phone = dto.phoneNo;
    } else {
      phone = dto.mobileNo;
    }

    return ContactPerson(
      name: dto.name,
      phoneNumber: concatenateRegexMatches(phone, phoneRegex),
    );
  }
}

enum DeliveryState { canceled, finished, ongoing, onhold }

class Discount {
  Discount({this.note, this.noteId, required this.article});

  String? note;
  String? noteId;

  /// The article refers to the special discount article such as "GUTSCHRIFT10"
  /// for example.
  Article article;
}

class ImageNote {
  ImageNote({
    required this.name,
    required this.url,
    required this.objectId,
    this.data
  });

  final String name;
  final String url;
  final String objectId;
  Uint8List? data;

  factory ImageNote.fromDTO(ImageNoteDTO dto) {
    return ImageNote(name: dto.name, url: dto.url, objectId: dto.oid);
  }

  factory ImageNote.make(String objectId, String name, Uint8List? bytes) {
    String url = "/v1/preview/1920_1080_100_png/$objectId";

    return ImageNote(name: name, url: url, objectId: objectId, data: bytes);
  }
}

class DeliveryOption {
  DeliveryOption({
    required this.numerical,
    required this.value,
    required this.display,
    required this.key,
  });

  bool numerical;
  String value;
  String display;
  String key;

  factory DeliveryOption.fromDTO(DeliveryOptionDTO dto) {
    return DeliveryOption(
      numerical: dto.numerical,
      value: dto.value,
      display: dto.display,
      key: dto.key,
    );
  }

  dynamic getValue() {
    if (!numerical) {
      if (value.isEmpty) {
        return false;
      } else {
        return value == "0" ? false : true;
      }
    } else {
      if (value.isEmpty) {
        return 0;
      } else {
        return int.parse(value);
      }
    }
  }

  DeliveryOption copyWith({
    bool? numerical,
    String? value,
    String? display,
    String? key,
  }) {
    return DeliveryOption(
      numerical: numerical ?? this.numerical,
      value: value ?? this.value,
      display: display ?? this.display,
      key: key ?? this.key,
    );
  }
}

class Delivery implements Comparable<Delivery> {
  Delivery({
    required this.customer,
    required this.id,
    required this.articles,
    required this.paymentOptions,
    required this.notes,
    required this.price,
    required this.filePaths,
    required this.currency,
    required this.images,
    required this.prepayment,
    required this.paymentAtDelivery,
    required this.payment,
    required this.options,
    this.state = DeliveryState.ongoing,
    this.contactPerson,
    required this.totalGrossValue,
    required this.totalNetValue,
    this.desiredTime,
    this.discount,
    this.specialAgreements,
    this.carId,
  });

  final Customer customer;
  final String id;
  List<Article> articles;
  final ContactPerson? contactPerson;
  final double price;
  final String currency;
  double totalGrossValue;
  double totalNetValue;
  String? desiredTime;
  List<String> filePaths;
  Discount? discount;
  DeliveryState state;
  String? specialAgreements;
  PaymentOptions paymentOptions;
  int? carId;
  List<Note> notes;
  List<ImageNote> images;
  double prepayment;
  double paymentAtDelivery;
  Payment payment;
  List<DeliveryOption> options;

  @override
  int compareTo(Delivery other) {
    return customer.name.compareTo(other.customer.name);
  }

  Delivery copyWith({
    Customer? customer,
    String? id,
    List<Article>? articles,
    ContactPerson? contactPerson,
    double? price,
    String? currency,
    double? totalGrossValue,
    double? totalNetValue,
    String? desiredTime,
    List<String>? filePaths,
    Discount? discount,
    DeliveryState? state,
    String? specialAgreements,
    PaymentOptions? paymentOptions,
    int? carId,
    List<Note>? notes,
    List<ImageNote>? images,
    double? prepayment,
    double? paymentAtDelivery,
    Payment? payment,
    List<DeliveryOption>? options,
  }) {
    return Delivery(
      customer: customer ?? this.customer,
      id: id ?? this.id,
      articles: articles ?? this.articles,
      contactPerson: contactPerson ?? this.contactPerson,
      price: price ?? this.price,
      currency: currency ?? this.currency,
      totalGrossValue: totalGrossValue ?? this.totalGrossValue,
      totalNetValue: totalNetValue ?? this.totalNetValue,
      desiredTime: desiredTime ?? this.desiredTime,
      filePaths: filePaths ?? this.filePaths,
      discount: discount ?? this.discount,
      state: state ?? this.state,
      specialAgreements: specialAgreements ?? this.specialAgreements,
      paymentOptions: paymentOptions ?? this.paymentOptions,
      carId: carId ?? this.carId,
      notes: notes ?? this.notes,
      images: images ?? this.images,
      prepayment: prepayment ?? this.prepayment,
      paymentAtDelivery: paymentAtDelivery ?? this.paymentAtDelivery,
      payment: payment ?? this.payment,
      options: options ?? this.options,
    );
  }

  Article? findArticleWithNoteId(String noteId) {
    if (discount != null && discount?.noteId == noteId) {
      return discount?.article;
    }

    int index = articles.indexWhere((article) => article.removeNoteId == noteId);
    // If no article with an according remove note id is found, skip this step.
    if (index == -1) {
      return null;
    }

    return articles[index];
  }

  double getGrossPrice() {
    return articles.fold(0, (acc, article) {
      double price = article.getGrossPriceScanned();

      if (!article.scannable) {
        price = article.getGrossPrice();
      }

      return acc + price;
    });
  }

  double getOpenPrice() {
    return getGrossPrice() - prepayment;
  }

  List<Article> getDeliveredArticles() {
    return articles
        .where((article) {
          if (!article.scannable) return true;
          if (article.isParent && article.components.isNotEmpty) {
            return article.isFullyScanned;
          }
          return article.scannedAmount > 0;
        })
        .toList();
  }

  bool containsArticle(String articleNr) {
    return articles.any((article) => article.hasArticleNumber(articleNr));
  }

  Article getArticle(String nr) {
    return articles.firstWhere((article) => article.articleNumber == nr);
  }

  /// Find the parent article whose BOM contains [componentArticleNr].
  Article? findParentOfComponent(String componentArticleNr) {
    for (final article in articles) {
      if (article.isParent &&
          article.findComponent(componentArticleNr) != null) {
        return article;
      }
    }
    return null;
  }

  List<Article> getScannableArticles() {
    return articles.where((article) => article.scannable).toList();
  }

  bool allArticlesScanned() {
    return getScannableArticles().every(
      (article) => article.isFullyScanned,
    );
  }

  void scanArticle(String nr) {
    if (!containsArticle(nr)) {
      return;
    }

    Article article = getArticle(nr);
    if (article.scannedAmount < article.amount) {
      article.scannedAmount += 1;
    }
  }

  factory Delivery.fromDTO(DeliveryDTO dto) {
    double getPrice() {
      return double.parse(dto.totalPrice == "" ? "0" : dto.totalPrice);
    }

    return Delivery(
      customer: Customer.fromDTO(dto.customer),
      id: dto.internalReceiptNo,
      articles: dto.articles.map(Article.fromDTO).toList(),
      paymentOptions: PaymentOptions(value: getPrice()),
      notes: dto.notes.map(Note.fromDto).toList(),
      price: getPrice(),
      payment: Payment.fromDTO(dto.payment),
      specialAgreements: dto.specialAggreements,
      desiredTime: dto.desiredTime,
      prepayment: double.tryParse(dto.prepayment) ?? 0.0,
      discount:
          dto.discount == null
              ? null
              : Discount(
                article: Article.fromDTO(dto.discount!.article),
                note: dto.discount!.note,
                noteId: dto.discount!.noteId,
              ),
      paymentAtDelivery: double.tryParse(dto.paymentAtDelivery) ?? 0.0,
      images: dto.images.map(ImageNote.fromDTO).toList(),
      carId: int.tryParse(dto.carId),
      totalGrossValue: double.parse(
        dto.totalGrossValue == "" ? "0" : dto.totalGrossValue,
      ),
      totalNetValue: double.parse(
        dto.totalNetValue == "" ? "0" : dto.totalNetValue,
      ),
      filePaths: [],
      options:
          dto.options.map((option) => DeliveryOption.fromDTO(option)).toList(),
      state:
          dto.state != ""
              ? getDeliveryStateFromString(dto.state)
              : DeliveryState.ongoing,
      contactPerson: ContactPerson.fromDTO(dto.contactPerson),
      currency: dto.currency ?? "EUR",
    );
  }
}
