import 'dart:convert';

import 'package:flutter/foundation.dart';

import 'package:logging/logging.dart';

import 'package:uuid/uuid.dart';

import 'package:money2/money2.dart';

import 'package:pshared/api/requests/payment/quote.dart';
import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/data/mapper/payment/intent/payment.dart';
import 'package:pshared/models/payment/intent.dart';
import 'package:pshared/models/payment/kind.dart';
import 'package:pshared/models/payment/quote/quote.dart';
import 'package:pshared/models/auto_refresh_mode.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/payment/auto_refresh.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/payment/quote_helpers.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;
  String? _sourceCurrencyCode;
  final QuotationIntentBuilder _intentBuilder = QuotationIntentBuilder();
  final AutoRefreshScheduler _autoRefresh = AutoRefreshScheduler();
  AutoRefreshMode _autoRefreshMode = AutoRefreshMode.on;

  void update(
    OrganizationsProvider venue,
    PaymentAmountProvider payment,
    PaymentSourceController source,
    PaymentFlowProvider flow,
    RecipientsProvider recipients,
    PaymentMethodsProvider _,
  ) {
    _organizations = venue;
    _sourceCurrencyCode = source.selectedCurrencyCode;
    final builtIntent = _intentBuilder.build(
      payment: payment,
      source: source,
      flow: flow,
      recipients: recipients,
    );
    if (!_isQuotable(builtIntent)) {
      _clearQuotation();
      return;
    }
    final intent = builtIntent!;
    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;
  AutoRefreshMode get autoRefreshMode => _autoRefreshMode;

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

  Money? get fee => quoteFeeTotal(quotation);
  Money? get total => quoteSourceDebitTotal(
    quotation,
    preferredSourceCurrency: _sourceCurrencyCode,
  );
  Money? get recipientGets => quotation?.amounts?.destinationSettlement;

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

  void setAutoRefreshMode(AutoRefreshMode mode) {
    if (_autoRefreshMode == mode) return;
    _autoRefreshMode = mode;
    _syncAutoRefresh();
    notifyListeners();
  }

  Future<PaymentQuote?> refreshQuotation() async {
    final lastIntent = _lastIntent;
    if (!_isQuotable(lastIntent)) {
      _clearQuotation();
      return null;
    }
    return getQuotation(lastIntent!);
  }

  Future<PaymentQuote?> getQuotation(PaymentIntent intent) async {
    if (!_isQuotable(intent)) {
      _clearQuotation();
      return null;
    }
    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;
    _sourceCurrencyCode = null;
    _clearQuotation();
  }

  bool _isQuotable(PaymentIntent? intent) {
    if (intent == null) return false;
    if (intent.kind == PaymentKind.unspecified) return false;
    if (intent.source == null) return false;
    if (intent.destination == null) return false;
    return intent.amount != null;
  }

  void _clearQuotation() {
    _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');
  }

  void _syncAutoRefresh() {
    final isEnabled = _autoRefreshMode == AutoRefreshMode.on;
    _autoRefresh.setEnabled(isEnabled);
    final canAutoRefresh = isEnabled && canRefresh;
    _autoRefresh.sync(
      isLoading: isLoading,
      canRefresh: canAutoRefresh,
      scheduledAt: quoteExpiresAt,
      onRefresh: () async {
        await refreshQuotation();
      },
    );
  }

  @override
  void dispose() {
    _autoRefresh.dispose();
    super.dispose();
  }
}
