import 'package:collection/collection.dart';
import 'package:flutter/foundation.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/provider/recipient/provider.dart';
import 'package:pshared/provider/recipient/pmethods.dart';


class PaymentFlowProvider extends ChangeNotifier {
  PaymentType _selectedType;
  PaymentType? _preferredType;
  PaymentMethodData? _manualPaymentData;
  List<PaymentMethod> _recipientMethods = [];
  Recipient? _recipient;
  String? _selectedMethodId;

  PaymentFlowProvider({
    required PaymentType initialType,
    PaymentType? preferredType,
  })  : _selectedType = initialType,
        _preferredType = preferredType ?? initialType;

  PaymentType get selectedType => _selectedType;
  PaymentMethodData? get manualPaymentData => _manualPaymentData;
  Recipient? get recipient => _recipient;
  PaymentMethod? get selectedMethod {
    if (!hasRecipient) return null;
    if (_selectedMethodId != null) {
      final byId = _recipientMethods.firstWhereOrNull((method) => method.id == _selectedMethodId);
      if (byId != null) return byId;
    }
    return _preferredMethodForType(_selectedType, _recipientMethods);
  }

  bool get hasRecipient => _recipient != null;

  MethodMap get availableTypes => hasRecipient
      ? _buildAvailableTypes(_recipientMethods)
      : {for (final type in PaymentType.values) type: null};

  PaymentMethodData? get selectedPaymentData =>
      hasRecipient ? selectedMethod?.data : _manualPaymentData;

  List<PaymentMethod> get methodsForRecipient => hasRecipient
      ? List<PaymentMethod>.unmodifiable(_recipientMethods)
      : const [];

  List<PaymentMethod> get methodsForSelectedType => hasRecipient
      ? List<PaymentMethod>.unmodifiable(
          _recipientMethods.where((method) => method.type == _selectedType).toList(),
        )
      : const [];

  void update(
    RecipientsProvider recipientsProvider,
    PaymentMethodsProvider methodsProvider,
  ) =>
      _applyState(
        recipient: recipientsProvider.currentObject,
        methods: methodsProvider.methodsForRecipient(recipientsProvider.currentObject),
        preferredType: _preferredType,
        forceResetManualData: false,
      );

  void selectType(PaymentType type, {bool resetManualData = false}) {
    if (hasRecipient && !availableTypes.containsKey(type)) {
      return;
    }

    if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) {
      return;
    }

    _selectedType = type;
    if (hasRecipient) {
      _selectedMethodId = _preferredMethodForType(type, _recipientMethods)?.id;
    }
    if (resetManualData) {
      _manualPaymentData = null;
    }
    notifyListeners();
  }

  void selectMethod(PaymentMethod method) {
    if (!hasRecipient) return;
    if (_selectedMethodId == method.id && _selectedType == method.type) return;
    _selectedMethodId = method.id;
    if (_selectedType != method.type) {
      _selectedType = method.type;
    }
    notifyListeners();
  }

  void setManualPaymentData(PaymentMethodData? data) {
    _manualPaymentData = data;
    notifyListeners();
  }

  void setPreferredType(PaymentType? preferredType) {
    if (_preferredType == preferredType) {
      return;
    }

    _preferredType = preferredType;
    _applyState(
      recipient: _recipient,
      methods: _recipientMethods,
      preferredType: _preferredType,
      forceResetManualData: false,
    );
  }

  PaymentType _resolveSelectedType({
    required Recipient? recipient,
    required MethodMap availableTypes,
    PaymentType? preferredType,
  }) {
    if (recipient == null) {
      return preferredType ?? _selectedType;
    }

    if (availableTypes.isEmpty) {
      return preferredType ?? PaymentType.bankAccount;
    }

    if (availableTypes.keys.contains(_selectedType)) {
      return _selectedType;
    }

    if (preferredType != null && availableTypes.keys.contains(preferredType)) {
      return preferredType;
    }

    return availableTypes.keys.first;
  }

  void _applyState({
    required Recipient? recipient,
    required List<PaymentMethod> methods,
    required PaymentType? preferredType,
    required bool forceResetManualData,
  }) {
    final availableTypes = _buildAvailableTypes(methods);
    final resolvedType = _resolveSelectedType(
      recipient: recipient,
      availableTypes: availableTypes,
      preferredType: preferredType,
    );
    final resolvedMethod = _resolveSelectedMethod(
      recipient: recipient,
      methods: methods,
      selectedType: resolvedType,
      selectedMethodId: _selectedMethodId,
    );

    var hasChanges = false;

    if (_recipient != recipient) {
      _recipient = recipient;
      hasChanges = true;
    }

    if (!_hasSameMethods(methods)) {
      _recipientMethods = methods;
      hasChanges = true;
    }

    if (resolvedType != _selectedType) {
      _selectedType = resolvedType;
      hasChanges = true;
    }

    if ((resolvedMethod?.id ?? _selectedMethodId) != _selectedMethodId) {
      _selectedMethodId = resolvedMethod?.id;
      hasChanges = true;
    }

    if ((recipient != null || forceResetManualData) && _manualPaymentData != null) {
      _manualPaymentData = null;
      hasChanges = true;
    }

    if (hasChanges) notifyListeners();
  }

  MethodMap _buildAvailableTypes(List<PaymentMethod> methods) => {
        for (final method in methods) method.type: method.data,
      };

  PaymentMethod? _preferredMethodForType(PaymentType type, List<PaymentMethod> methods) {
    final forType = methods.where((method) => method.type == type).toList();
    if (forType.isEmpty) return null;
    return forType.firstWhereOrNull((method) => method.isMain) ?? forType.first;
  }

  PaymentMethod? _resolveSelectedMethod({
    required Recipient? recipient,
    required List<PaymentMethod> methods,
    required PaymentType selectedType,
    required String? selectedMethodId,
  }) {
    if (recipient == null) return null;
    final forType = methods.where((method) => method.type == selectedType).toList();
    if (forType.isEmpty) return null;
    if (selectedMethodId != null) {
      final byId = forType.firstWhereOrNull((method) => method.id == selectedMethodId);
      if (byId != null) return byId;
    }
    return _preferredMethodForType(selectedType, methods);
  }

  bool _hasSameMethods(List<PaymentMethod> methods) {
    if (_recipientMethods.length != methods.length) return false;
    for (var i = 0; i < methods.length; i++) {
      final current = _recipientMethods[i];
      final next = methods[i];
      if (current.id != next.id || current.updatedAt != next.updatedAt) return false;
    }
    return true;
  }
}
