import 'package:collection/collection.dart';

import 'package:pshared/data/mapper/payment/method.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/organization/bound.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/type.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/models/permissions/bound.dart';
import 'package:pshared/models/storable.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pshared/provider/template.dart';
import 'package:pshared/service/recipient/pmethods.dart';


class PaymentMethodsProvider extends GenericProvider<PaymentMethod> {
  late OrganizationsProvider _organizations;

  PaymentMethodsProvider() : super(service: PaymentMethodService.basicService);

  List<PaymentMethod> get methods => List<PaymentMethod>.unmodifiable(items.toList()..sort((a, b) => a.storable.createdAt.compareTo(b.storable.createdAt))); 

  List<PaymentMethod> methodsForRecipient(Recipient? recipient) {
    if (recipient == null || !isReady) return [];

    return methods
        .where((method) => !method.isArchived && method.recipientRef == recipient.id)
        .toList();
  }

  MethodMap availableTypesForRecipient(Recipient? recipient) => {
    for (final method in methodsForRecipient(recipient)) method.type: method.data,
  };

  PaymentMethod? findMethodByType({
    required PaymentType type,
    required Recipient? recipient,
  }) =>
      methodsForRecipient(recipient).firstWhereOrNull((method) => method.type == type);

  void updateProviders(OrganizationsProvider organizations, RecipientsProvider recipients) {
    if (recipients.currentObject != null) loadMethods(organizations, recipients.currentObject?.id);
  }

  // TODO: current providers structure forces to use weird construction with in-place PaymentMethodsProviders
  // it would be better to load reipients together with their payment methods in RecipientsProvider
  Future<void> loadMethods(OrganizationsProvider organizations, String? recipientRef) async {
    _organizations = organizations;
    if (_organizations.isOrganizationSet && (recipientRef != null)) {
      return load(_organizations.current.id, recipientRef);
    }
  }

  // void reorderMethods(int oldIndex, int newIndex) {
  //   if (newIndex > oldIndex) newIndex--;
  //   final item = _methods.removeAt(oldIndex);
  //   _methods.insert(newIndex, item);
  //   notifyListeners();
  // }

  PaymentMethod? get main => methods.firstWhereOrNull((m) => m.isMain);

  Future<void> updateMethod(PaymentMethod method) async => update(method.toDTO().toJson());

  Future<void> setArchivedMethod({
    required PaymentMethod method, 
    required bool newIsArchived, 
  }) async => setArchived(
    organizationRef: _organizations.current.id,
    objectRef: method.id,
    newIsArchived: newIsArchived,
    cascade: true,
  );

  Future<PaymentMethod> create({
    required String reacipientRef,
    required PaymentMethodData data,
    required String name,
  }) => createObject(
    _organizations.current.id,
    PaymentMethod(
      storable: newStorable(),
      permissionBound: newPermissionBound(
        organizationBound: newOrganizationBound(organizationRef: _organizations.current.id),
      ),
      recipientRef: reacipientRef,
      data: data,
      describable: newDescribable(name: name),
    ).toDTO().toJson(),
  );

  Future<void> makeMain(PaymentMethod method) {
    // TODO: create separate backend method to manage main payment method
    final updates = <Future<void>>[];
    final currentMain = main;
    if (currentMain != null) {
      updates.add(updateMethod(currentMain.copyWith(isMain: false)));
    }
    updates.add(updateMethod(method.copyWith(isMain: true)));
    return Future.wait(updates).then((_) => null);
  }
}
