import 'dart:convert';

import 'package:flutter/foundation.dart';

import 'package:logging/logging.dart';

import 'package:uuid/uuid.dart';

import 'package:pshared/api/requests/payment/quote.dart';
import 'package:pshared/controllers/balance_mask/wallets.dart';
import 'package:pshared/data/mapper/payment/intent/payment.dart';
import 'package:pshared/models/asset.dart';
import 'package:pshared/models/payment/intent.dart';
import 'package:pshared/models/payment/quote/quote.dart';
import 'package:pshared/models/money.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/payment/amount.dart';
import 'package:pshared/provider/payment/flow.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pshared/provider/recipient/pmethods.dart';
import 'package:pshared/provider/resource.dart';
import 'package:pshared/provider/payment/quotation/intent_builder.dart';
import 'package:pshared/service/payment/quotation.dart';
import 'package:pshared/utils/exception.dart';


class QuotationProvider extends ChangeNotifier {
  static final _logger = Logger('provider.payment.quotation');
  Resource<PaymentQuote> _quotation = Resource(data: null, isLoading: false, error: null);
  late OrganizationsProvider _organizations;
  bool _isLoaded = false;
  PaymentIntent? _lastIntent;
  final QuotationIntentBuilder _intentBuilder = QuotationIntentBuilder();

  void update(
    OrganizationsProvider venue, 
    PaymentAmountProvider payment,
    WalletsController wallets,
    PaymentFlowProvider flow,
    RecipientsProvider recipients,
    PaymentMethodsProvider _,
  ) {
    _organizations = venue;
    final intent = _intentBuilder.build(
      payment: payment,
      wallets: wallets,
      flow: flow,
      recipients: recipients,
    );
    if (intent == null) return;
    final intentKey = _buildIntentKey(intent);
    final lastIntent = _lastIntent;
    if (lastIntent != null && intentKey == _buildIntentKey(lastIntent)) return;
    getQuotation(intent);
  }

  PaymentQuote? get quotation => _quotation.data;
  bool get isLoading => _quotation.isLoading;
  Exception? get error => _quotation.error;
  bool get canRefresh => _lastIntent != null;
  bool get isReady => _isLoaded && !_quotation.isLoading && _quotation.error == null;

  DateTime? get quoteExpiresAt {
    final expiresAtUnixMs = quotation?.fxQuote?.expiresAtUnixMs;
    if (expiresAtUnixMs == null) return null;
    return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true);
  }


  Asset? get fee => _assetFromMoney(quotation?.expectedFeeTotal);
  Asset? get total => _assetFromMoney(quotation?.debitAmount);
  Asset? get recipientGets => _assetFromMoney(quotation?.expectedSettlementAmount);

  Asset? _assetFromMoney(Money? money) {
    if (money == null) return null;
    return createAsset(money.currency, money.amount);
  }

  void _setResource(Resource<PaymentQuote> quotation) {
    _quotation = quotation;
    notifyListeners();
  }

  Future<PaymentQuote?> refreshQuotation() async {
    final intent = _lastIntent;
    if (intent == null) return null;
    return getQuotation(intent);
  }

  Future<PaymentQuote?> getQuotation(PaymentIntent intent) async {
    if (!_organizations.isOrganizationSet) throw StateError('Organization is not set');
    _lastIntent = intent;
    try {
      _setResource(_quotation.copyWith(isLoading: true, error: null));
      final response = await QuotationService.getQuotation(
        _organizations.current.id, 
        QuotePaymentRequest(
          idempotencyKey: Uuid().v4(),
          intent: intent.toDTO(),
        ),
      );
      _isLoaded = true;
      _setResource(_quotation.copyWith(data: response, isLoading: false, error: null));
    } catch (e, st) {
      _logger.warning('Failed to get quotation', e, st);
      _setResource(_quotation.copyWith(
        data: null,
        error: toException(e),
        isLoading: false,
      ));
    }
    return _quotation.data;
  }

  void reset() {
    _isLoaded = false;
    _lastIntent = null;
    _setResource(Resource(data: null, isLoading: false, error: null));
  }

  String _buildIntentKey(PaymentIntent intent) {
    final payload = jsonEncode(intent.toDTO().toJson());
    return Uuid().v5(Namespace.url.value, 'quote:$payload');
  }
}
