import 'package:flutter/foundation.dart';

import 'package:money2/money2.dart';

import 'package:pshared/models/payment/payment.dart';
import 'package:pshared/models/payment/quote/quote.dart';
import 'package:pshared/models/payment/quote/status_type.dart';
import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/provider/payment/multiple/provider.dart';
import 'package:pshared/provider/payment/multiple/quotation.dart';
import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
import 'package:pshared/utils/payment/quote_helpers.dart';

import 'package:pweb/models/payment/multiple_payouts/csv_row.dart';
import 'package:pweb/models/payment/multiple_payouts/state.dart';
import 'package:pweb/utils/payment/multiple/csv_parser.dart';
import 'package:pweb/utils/payment/multiple/intent_builder.dart';
import 'package:pweb/utils/payment/upload_metadata.dart';


class MultiplePayoutsProvider extends ChangeNotifier {
  final MultipleCsvParser _csvParser;
  final MultipleIntentBuilder _intentBuilder;

  MultiQuotationProvider? _quotation;
  MultiPaymentProvider? _payment;

  MultiplePayoutsState _state = MultiplePayoutsState.idle;
  String? _selectedFileName;
  List<CsvPayoutRow> _rows = const <CsvPayoutRow>[];
  int _sentCount = 0;
  Exception? _error;

  MultiplePayoutsProvider({
    MultipleCsvParser? csvParser,
    MultipleIntentBuilder? intentBuilder,
  }) : _csvParser = csvParser ?? MultipleCsvParser(),
       _intentBuilder = intentBuilder ?? MultipleIntentBuilder();

  void update(MultiQuotationProvider quotation, MultiPaymentProvider payment) {
    _bindQuotation(quotation);
    _payment = payment;
  }

  MultiplePayoutsState get state => _state;
  String? get selectedFileName => _selectedFileName;
  List<CsvPayoutRow> get rows => List.unmodifiable(_rows);
  int get sentCount => _sentCount;
  Exception? get error => _error;

  bool get isQuoting => _state == MultiplePayoutsState.quoting;
  bool get isSending => _state == MultiplePayoutsState.sending;
  bool get isBusy => isQuoting || isSending;

  bool get quoteIsLoading => _quotation?.isLoading ?? false;

  QuoteStatusType get quoteStatusType {
    final quotation = _quotation;
    if (quotation == null) return QuoteStatusType.missing;
    if (quotation.isLoading) return QuoteStatusType.loading;
    if (quotation.error != null) return QuoteStatusType.error;
    if (quotation.quotation == null) return QuoteStatusType.missing;
    if (_isQuoteExpired(quotation.quoteExpiresAt)) {
      return QuoteStatusType.expired;
    }
    return QuoteStatusType.active;
  }

  Duration? get quoteTimeLeft {
    final expiresAt = _quotation?.quoteExpiresAt;
    if (expiresAt == null) return null;
    return expiresAt.difference(DateTime.now().toUtc());
  }

  bool get canSend {
    if (isBusy || _rows.isEmpty) return false;
    final quoteRef = _quotation?.quotation?.quoteRef;
    return quoteRef != null && quoteRef.isNotEmpty;
  }

  Money? aggregateDebitAmountForCurrency(String? sourceCurrencyCode) {
    if (_rows.isEmpty) return null;
    final totals = aggregateMoneyByCurrency(
      _quoteItems().map((quote) => quote.amounts?.sourceDebitTotal),
    );
    return _moneyForSourceCurrency(totals, sourceCurrencyCode);
  }

  Money? get requestedSentAmount {
    if (_rows.isEmpty) return null;
    const currency = 'RUB';
    final rubCurrency = money2CurrencyFromCode(currency);
    if (rubCurrency == null) return null;

    Money? total;
    for (final row in _rows) {
      final value = parseMoneyWithCurrency(row.amount, rubCurrency);
      if (value == null) return null;
      total = total == null ? value : total + value;
    }
    return total;
  }

  Money? aggregateSettlementAmountForCurrency(String? sourceCurrencyCode) {
    if (_rows.isEmpty) return null;
    final totals = aggregateMoneyByCurrency(
      _quoteItems().map((quote) => quote.amounts?.destinationSettlement),
    );
    return _moneyForSourceCurrency(totals, sourceCurrencyCode);
  }

  Money? aggregateFeeAmountForCurrency(String? sourceCurrencyCode) {
    if (_rows.isEmpty) return null;
    final totals = aggregateMoneyByCurrency(_quoteItems().map(quoteFeeTotal));
    return _moneyForSourceCurrency(totals, sourceCurrencyCode);
  }

  double? aggregateFeePercentForCurrency(String? sourceCurrencyCode) {
    final debit = aggregateDebitAmountForCurrency(sourceCurrencyCode);
    final fee = aggregateFeeAmountForCurrency(sourceCurrencyCode);
    if (debit == null || fee == null) return null;

    final debitValue = debit;
    final feeValue = fee;
    if (debitValue.currency.isoCode != feeValue.currency.isoCode) return null;
    if (!debitValue.isPositive) return null;
    return (feeValue.toDecimal() / debitValue.toDecimal()).toDouble() * 100;
  }

  Future<void> quoteFromCsv({
    required String fileName,
    required String content,
    required PaymentMethodData sourceMethod,
    required String sourceCurrencyCode,
  }) async {
    if (isBusy) return;

    final quotation = _quotation;
    if (quotation == null) {
      _setErrorObject(
        StateError('Multiple payouts dependencies are not ready'),
      );
      return;
    }

    try {
      _setState(MultiplePayoutsState.quoting);
      _error = null;
      _sentCount = 0;

      final rows = _csvParser.parseRows(content);
      await _quoteRows(
        quotation: quotation,
        fileName: fileName,
        rows: rows,
        sourceMethod: sourceMethod,
        sourceCurrencyCode: sourceCurrencyCode,
      );

      if (quotation.error != null) {
        _setErrorObject(quotation.error!);
      }
    } catch (e) {
      _setErrorObject(e);
    } finally {
      _setState(MultiplePayoutsState.idle);
    }
  }

  Future<void> requoteUploadedRows({
    required PaymentMethodData sourceMethod,
    required String sourceCurrencyCode,
  }) async {
    if (isBusy || _rows.isEmpty || _selectedFileName == null) return;
    final quotation = _quotation;
    if (quotation == null) return;

    try {
      _setState(MultiplePayoutsState.quoting);
      _error = null;
      _sentCount = 0;

      await _quoteRows(
        quotation: quotation,
        fileName: _selectedFileName!,
        rows: _rows,
        sourceMethod: sourceMethod,
        sourceCurrencyCode: sourceCurrencyCode,
      );

      if (quotation.error != null) {
        _setErrorObject(quotation.error!);
      }
    } catch (e) {
      _setErrorObject(e);
    } finally {
      _setState(MultiplePayoutsState.idle);
    }
  }

  Future<List<Payment>> send() async {
    if (isBusy) return const <Payment>[];

    final payment = _payment;
    if (payment == null) {
      _setErrorObject(
        StateError('Multiple payouts payment provider is not ready'),
      );
      return const <Payment>[];
    }
    if (!canSend) {
      _setErrorObject(
        StateError('Upload CSV and wait for quote before sending'),
      );
      return const <Payment>[];
    }

    try {
      _setState(MultiplePayoutsState.sending);
      _error = null;

      final result = await payment.pay(
        metadata: <String, String>{
          ...?_selectedFileName == null
              ? null
              : <String, String>{
                  UploadMetadataKeys.fileName: _selectedFileName!,
                },
          UploadMetadataKeys.rows: _rows.length.toString(),
          ...?_uploadAmountMetadata(),
        },
      );

      _sentCount = result.length;
      return result;
    } catch (e) {
      _setErrorObject(e);
      return const <Payment>[];
    } finally {
      _setState(MultiplePayoutsState.idle);
    }
  }

  void removeUploadedFile() {
    if (isBusy) return;

    _selectedFileName = null;
    _rows = const <CsvPayoutRow>[];
    _sentCount = 0;
    _error = null;
    notifyListeners();
  }

  void _setState(MultiplePayoutsState value) {
    _state = value;
    notifyListeners();
  }

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

  void _bindQuotation(MultiQuotationProvider quotation) {
    if (identical(_quotation, quotation)) return;
    _quotation?.removeListener(_onQuotationChanged);
    _quotation = quotation;
    _quotation?.addListener(_onQuotationChanged);
  }

  void _onQuotationChanged() {
    notifyListeners();
  }

  bool _isQuoteExpired(DateTime? expiresAt) {
    if (expiresAt == null) return false;
    return expiresAt.difference(DateTime.now().toUtc()) <= Duration.zero;
  }

  Map<String, String>? _uploadAmountMetadata() {
    final sentAmount = requestedSentAmount;
    if (sentAmount == null) return null;
    return <String, String>{
      UploadMetadataKeys.amount: sentAmount.toDecimal().toString(),
      UploadMetadataKeys.currency: sentAmount.currency.isoCode,
    };
  }

  Money? _moneyForSourceCurrency(
    List<Money>? values,
    String? sourceCurrencyCode,
  ) {
    if (values == null || values.isEmpty) return null;

    final sourceCurrency =
        money2CurrencyFromCode(sourceCurrencyCode)?.isoCode ??
        sourceCurrencyCode;
    if (sourceCurrency != null) {
      for (final value in values) {
        if (value.currency.isoCode == sourceCurrency) {
          return value;
        }
      }
    }

    return values.first;
  }

  List<PaymentQuote> _quoteItems() =>
      _quotation?.quotation?.items ?? const <PaymentQuote>[];

  Future<void> _quoteRows({
    required MultiQuotationProvider quotation,
    required String fileName,
    required List<CsvPayoutRow> rows,
    required PaymentMethodData sourceMethod,
    required String sourceCurrencyCode,
  }) async {
    final intents = _intentBuilder.buildIntents(
      sourceMethod: sourceMethod,
      sourceCurrency: sourceCurrencyCode,
      rows: rows,
    );

    _selectedFileName = fileName;
    _rows = rows;

    await quotation.quotePayments(
      intents,
      metadata: <String, String>{
        UploadMetadataKeys.fileName: fileName,
        UploadMetadataKeys.rows: rows.length.toString(),
        ...?_uploadAmountMetadata(),
      },
    );
  }

  @override
  void dispose() {
    _quotation?.removeListener(_onQuotationChanged);
    super.dispose();
  }
}
