import 'dart:async';

import 'package:flutter/foundation.dart';

import 'package:money2/money2.dart';

import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/payment/asset.dart';
import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/ledger.dart';
import 'package:pshared/models/payment/methods/managed_wallet.dart';
import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/models/payment/quote/status_type.dart';

import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
import 'package:pweb/models/payment/multiple_payouts/state.dart';
import 'package:pweb/providers/multiple_payouts.dart';
import 'package:pweb/services/payments/csv_input.dart';


class MultiplePayoutsController extends ChangeNotifier {
  final CsvInputService _csvInput;
  MultiplePayoutsProvider? _provider;
  PaymentSourceController? _sourceController;
  _PickState _pickState = _PickState.idle;
  Exception? _uiError;
  String? _lastSourceKey;

  MultiplePayoutsController({required CsvInputService csvInput})
    : _csvInput = csvInput;

  void update(
    MultiplePayoutsProvider provider,
    PaymentSourceController sourceController,
  ) {
    var shouldNotify = false;
    if (!identical(_provider, provider)) {
      _provider?.removeListener(_onProviderChanged);
      _provider = provider;
      _provider?.addListener(_onProviderChanged);
      shouldNotify = true;
    }
    if (!identical(_sourceController, sourceController)) {
      _sourceController?.removeListener(_onSourceChanged);
      _sourceController = sourceController;
      _sourceController?.addListener(_onSourceChanged);
      _lastSourceKey = _currentSourceKey;
      shouldNotify = true;
    }
    if (shouldNotify) {
      notifyListeners();
    }
  }

  MultiplePayoutsState get state =>
      _provider?.state ?? MultiplePayoutsState.idle;
  String? get selectedFileName => _provider?.selectedFileName;
  List<CsvPayoutRow> get rows => _provider?.rows ?? const <CsvPayoutRow>[];
  int get sentCount => _provider?.sentCount ?? 0;
  Exception? get error => _uiError ?? _provider?.error;

  bool get isQuoting => _provider?.isQuoting ?? false;
  bool get isSending => _provider?.isSending ?? false;
  bool get isBusy => _provider?.isBusy ?? false;

  bool get quoteIsLoading => _provider?.quoteIsLoading ?? false;
  QuoteStatusType get quoteStatusType =>
      _provider?.quoteStatusType ?? QuoteStatusType.missing;
  Duration? get quoteTimeLeft => _provider?.quoteTimeLeft;

  bool get canSend => (_provider?.canSend ?? false) && _selectedSource != null;
  Money? get aggregateDebitAmount =>
      _provider?.aggregateDebitAmountForCurrency(_selectedSourceCurrencyCode);
  Money? get requestedSentAmount => _provider?.requestedSentAmount;
  Money? get aggregateSettlementAmount => _provider
      ?.aggregateSettlementAmountForCurrency(_selectedSourceCurrencyCode);
  Money? get aggregateFeeAmount =>
      _provider?.aggregateFeeAmountForCurrency(_selectedSourceCurrencyCode);
  double? get aggregateFeePercent =>
      _provider?.aggregateFeePercentForCurrency(_selectedSourceCurrencyCode);

  Future<void> pickAndQuote() async {
    if (_pickState == _PickState.picking) return;
    final provider = _provider;
    if (provider == null) {
      _setUiError(StateError('Multiple payouts provider is not ready'));
      return;
    }

    _clearUiError();
    _pickState = _PickState.picking;
    try {
      final picked = await _csvInput.pickCsv();
      if (picked == null) return;
      final source = _selectedSource;
      if (source == null) {
        _setUiError(StateError('Select source of funds first'));
        return;
      }
      await provider.quoteFromCsv(
        fileName: picked.name,
        content: picked.content,
        sourceMethod: source.method,
        sourceCurrencyCode: source.currencyCode,
      );
    } catch (e) {
      _setUiError(e);
    } finally {
      _pickState = _PickState.idle;
    }
  }

  Future<List<Payment>> send() async {
    return _provider?.send() ?? const <Payment>[];
  }

  Future<MultiplePayoutSendOutcome> sendAndGetOutcome() async {
    _clearUiError();
    final provider = _provider;
    if (provider == null) {
      _setUiError(StateError('Multiple payouts provider is not ready'));
      return MultiplePayoutSendOutcome.failure;
    }

    final payments = await provider.send();
    final hasError = provider.error != null;
    if (hasError || payments.isEmpty) {
      return MultiplePayoutSendOutcome.failure;
    }
    return MultiplePayoutSendOutcome.success;
  }

  void removeUploadedFile() {
    _provider?.removeUploadedFile();
    _clearUiError(notify: false);
  }

  void _onProviderChanged() {
    notifyListeners();
  }

  void _onSourceChanged() {
    final currentSourceKey = _currentSourceKey;
    final sourceChanged = currentSourceKey != _lastSourceKey;
    _lastSourceKey = currentSourceKey;
    if (sourceChanged) {
      unawaited(_requoteWithUploadedRows());
    }
    notifyListeners();
  }

  String? get _selectedSourceCurrencyCode =>
      _sourceController?.selectedCurrencyCode;
  String? get _currentSourceKey {
    final source = _sourceController;
    if (source == null ||
        source.selectedType == null ||
        source.selectedRef == null) {
      return null;
    }
    return '${source.selectedType!.name}:${source.selectedRef!}';
  }

  ({PaymentMethodData method, String currencyCode})? get _selectedSource {
    final source = _sourceController;
    if (source == null) return null;

    final currencyCode = source.selectedCurrencyCode;
    if (currencyCode == null || currencyCode.isEmpty) return null;

    final wallet = source.selectedWallet;
    if (wallet != null) {
      final hasAsset = (wallet.tokenSymbol ?? '').isNotEmpty;
      final asset = hasAsset
          ? PaymentAsset(
              chain: wallet.network ?? ChainNetwork.unspecified,
              tokenSymbol: wallet.tokenSymbol!,
              contractAddress: wallet.contractAddress,
            )
          : null;
      return (
        method: ManagedWalletPaymentMethod(
          managedWalletRef: wallet.id,
          asset: asset,
        ),
        currencyCode: currencyCode,
      );
    }

    final ledger = source.selectedLedgerAccount;
    if (ledger != null) {
      return (
        method: LedgerPaymentMethod(ledgerAccountRef: ledger.ledgerAccountRef),
        currencyCode: currencyCode,
      );
    }

    return null;
  }

  Future<void> _requoteWithUploadedRows() async {
    final provider = _provider;
    if (provider == null) return;
    if (provider.selectedFileName == null || provider.rows.isEmpty) return;

    final source = _selectedSource;
    if (source == null) return;

    _clearUiError(notify: false);
    await provider.requoteUploadedRows(
      sourceMethod: source.method,
      sourceCurrencyCode: source.currencyCode,
    );
  }

  void _setUiError(Object error) {
    _uiError = error is Exception ? error : Exception(error.toString());
    notifyListeners();
  }

  void _clearUiError({bool notify = true}) {
    if (_uiError == null) return;
    _uiError = null;
    if (notify) {
      notifyListeners();
    }
  }

  @override
  void dispose() {
    _provider?.removeListener(_onProviderChanged);
    _sourceController?.removeListener(_onSourceChanged);
    super.dispose();
  }
}

enum _PickState { idle, picking }

enum MultiplePayoutSendOutcome { success, failure }
