import 'package:decimal/decimal.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stackwallet/models/exchange/response_objects/currency.dart';
import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange.dart';
// import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
import 'package:stackwallet/utilities/logger.dart';

class ExchangeFormState extends ChangeNotifier {
  Exchange? _exchange;
  Exchange? get exchange => _exchange;
  set exchange(Exchange? value) {
    _exchange = value;
    _onExchangeTypeChanged();
  }

  ExchangeRateType _exchangeType = ExchangeRateType.estimated;
  ExchangeRateType get exchangeType => _exchangeType;
  set exchangeType(ExchangeRateType value) {
    _exchangeType = value;
    _onExchangeRateTypeChanged();
  }

  bool reversed = false;

  Decimal? fromAmount;
  Decimal? toAmount;

  Decimal? minAmount;
  Decimal? maxAmount;

  Decimal? rate;
  Estimate? estimate;

  FixedRateMarket? _market;
  FixedRateMarket? get market => _market;

  Currency? _from;
  Currency? _to;

  @override
  String toString() {
    return 'ExchangeFormState: {_exchange: $_exchange, _exchangeType: $_exchangeType, reversed: $reversed, fromAmount: $fromAmount, toAmount: $toAmount, minAmount: $minAmount, maxAmount: $maxAmount, rate: $rate, estimate: $estimate, _market: $_market, _from: $_from, _to: $_to, _onError: $_onError}';
  }

  String? get fromTicker {
    switch (exchangeType) {
      case ExchangeRateType.estimated:
        return _from?.ticker;
      case ExchangeRateType.fixed:
        switch (exchange?.name) {
          // case SimpleSwapExchange.exchangeName:
          //   return _from?.ticker;
          case ChangeNowExchange.exchangeName:
            return market?.from;
          default:
            return null;
        }
    }
  }

  String? get toTicker {
    switch (exchangeType) {
      case ExchangeRateType.estimated:
        return _to?.ticker;
      case ExchangeRateType.fixed:
        switch (exchange?.name) {
          // case SimpleSwapExchange.exchangeName:
          //   return _to?.ticker;
          case ChangeNowExchange.exchangeName:
            return market?.to;
          default:
            return null;
        }
    }
  }

  void Function(String)? _onError;

  Currency? get from => _from;
  Currency? get to => _to;

  void setCurrencies(Currency from, Currency to) {
    _from = from;
    _to = to;
  }

  String get warning {
    if (reversed) {
      if (toTicker != null && toAmount != null) {
        if (minAmount != null &&
            toAmount! < minAmount! &&
            toAmount! > Decimal.zero) {
          return "Minimum amount ${minAmount!.toString()} ${toTicker!.toUpperCase()}";
        } else if (maxAmount != null && toAmount! > maxAmount!) {
          return "Maximum amount ${maxAmount!.toString()} ${toTicker!.toUpperCase()}";
        }
      }
    } else {
      if (fromTicker != null && fromAmount != null) {
        if (minAmount != null &&
            fromAmount! < minAmount! &&
            fromAmount! > Decimal.zero) {
          return "Minimum amount ${minAmount!.toString()} ${fromTicker!.toUpperCase()}";
        } else if (maxAmount != null && fromAmount! > maxAmount!) {
          return "Maximum amount ${maxAmount!.toString()} ${fromTicker!.toUpperCase()}";
        }
      }
    }

    return "";
  }

  String get fromAmountString => fromAmount?.toStringAsFixed(8) ?? "";
  String get toAmountString => toAmount?.toStringAsFixed(8) ?? "";

  bool get canExchange {
    if (exchange?.name == ChangeNowExchange.exchangeName &&
        exchangeType == ExchangeRateType.fixed) {
      return _market != null &&
          fromAmount != null &&
          toAmount != null &&
          warning.isEmpty;
    } else {
      return fromAmount != null &&
          fromAmount != Decimal.zero &&
          toAmount != null &&
          rate != null &&
          warning.isEmpty;
    }
  }

  void clearAmounts(bool shouldNotifyListeners) {
    fromAmount = null;
    toAmount = null;
    minAmount = null;
    maxAmount = null;
    rate = null;

    if (shouldNotifyListeners) {
      notifyListeners();
    }
  }

  Future<void> setFromAmountAndCalculateToAmount(
    Decimal newFromAmount,
    bool shouldNotifyListeners,
  ) async {
    if (newFromAmount == Decimal.zero) {
      toAmount = Decimal.zero;
    }

    fromAmount = newFromAmount;
    reversed = false;

    await updateRanges(shouldNotifyListeners: false);

    await updateEstimate(
      shouldNotifyListeners: false,
      reversed: reversed,
    );

    if (shouldNotifyListeners) {
      notifyListeners();
    }
  }

  Future<void> setToAmountAndCalculateFromAmount(
    Decimal newToAmount,
    bool shouldNotifyListeners,
  ) async {
    if (newToAmount == Decimal.zero) {
      fromAmount = Decimal.zero;
    }

    toAmount = newToAmount;
    reversed = true;

    await updateRanges(shouldNotifyListeners: false);

    await updateEstimate(
      shouldNotifyListeners: false,
      reversed: reversed,
    );

    if (shouldNotifyListeners) {
      notifyListeners();
    }
  }

  Future<void> updateTo(Currency to, bool shouldNotifyListeners) async {
    try {
      _to = to;
      if (_from == null) {
        rate = null;
        notifyListeners();
        return;
      }

      await updateRanges(shouldNotifyListeners: false);

      await updateEstimate(
        shouldNotifyListeners: false,
        reversed: reversed,
      );

      //todo: check if print needed
      // debugPrint(
      //     "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange");

      if (shouldNotifyListeners) {
        notifyListeners();
      }
    } catch (e, s) {
      Logging.instance.log("$e\n$s", level: LogLevel.Error);
    }
  }

  Future<void> updateFrom(Currency from, bool shouldNotifyListeners) async {
    try {
      _from = from;

      if (_to == null) {
        rate = null;
        notifyListeners();
        return;
      }

      await updateRanges(shouldNotifyListeners: false);

      await updateEstimate(
        shouldNotifyListeners: false,
        reversed: reversed,
      );

      //todo: check if print needed
      // debugPrint(
      //     "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange");
      if (shouldNotifyListeners) {
        notifyListeners();
      }
    } catch (e, s) {
      Logging.instance.log("$e\n$s", level: LogLevel.Error);
    }
  }

  Future<void> updateMarket(
    FixedRateMarket? market,
    bool shouldNotifyListeners,
  ) async {
    _market = market;

    if (_market == null) {
      fromAmount = null;
      toAmount = null;
    } else {
      if (fromAmount != null) {
        if (fromAmount! <= Decimal.zero) {
          toAmount = Decimal.zero;
        } else {
          await updateRanges(shouldNotifyListeners: false);
          await updateEstimate(
            shouldNotifyListeners: false,
            reversed: reversed,
          );
        }
      }
    }

    if (shouldNotifyListeners) {
      notifyListeners();
    }
  }

  void _onExchangeRateTypeChanged() {
    print("_onExchangeRateTypeChanged");
    updateRanges(shouldNotifyListeners: true).then(
      (_) => updateEstimate(
        shouldNotifyListeners: true,
        reversed: reversed,
      ),
    );
  }

  void _onExchangeTypeChanged() {
    updateRanges(shouldNotifyListeners: true).then(
      (_) => updateEstimate(
        shouldNotifyListeners: true,
        reversed: reversed,
      ),
    );
  }

  Future<void> updateRanges({required bool shouldNotifyListeners}) async {
    // if (exchange?.name == SimpleSwapExchange.exchangeName) {
    //   reversed = false;
    // }
    final _fromTicker = reversed ? toTicker : fromTicker;
    final _toTicker = reversed ? fromTicker : toTicker;
    if (_fromTicker == null || _toTicker == null) {
      Logging.instance.log(
        "Tried to $runtimeType.updateRanges where (from: $_fromTicker || to: $_toTicker) for: $exchange",
        level: LogLevel.Info,
      );
      return;
    }
    final response = await exchange?.getRange(
      _fromTicker,
      _toTicker,
      exchangeType == ExchangeRateType.fixed,
    );

    if (response?.value == null) {
      Logging.instance.log(
        "Tried to $runtimeType.updateRanges for: $exchange where response: $response",
        level: LogLevel.Info,
      );
      return;
    }

    final range = response!.value!;

    minAmount = range.min;
    maxAmount = range.max;

    //todo: check if print needed
    // debugPrint(
    //     "updated range for: $exchange for $_fromTicker-$_toTicker: $range");

    if (shouldNotifyListeners) {
      notifyListeners();
    }
  }

  Future<void> updateEstimate({
    required bool shouldNotifyListeners,
    required bool reversed,
  }) async {
    // if (exchange?.name == SimpleSwapExchange.exchangeName) {
    //   reversed = false;
    // }
    final amount = reversed ? toAmount : fromAmount;
    if (fromTicker == null ||
        toTicker == null ||
        amount == null ||
        amount <= Decimal.zero) {
      Logging.instance.log(
        "Tried to $runtimeType.updateEstimate for: $exchange where (from: $fromTicker || to: $toTicker || amount: $amount)",
        level: LogLevel.Info,
      );
      return;
    }
    final response = await exchange?.getEstimate(
      fromTicker!,
      toTicker!,
      amount,
      exchangeType == ExchangeRateType.fixed,
      reversed,
    );

    if (response?.value == null) {
      Logging.instance.log(
        "Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
        level: LogLevel.Info,
      );
      return;
    }

    estimate = response!.value!;

    if (reversed) {
      fromAmount = estimate!.estimatedAmount;
    } else {
      toAmount = estimate!.estimatedAmount;
    }

    rate = (toAmount! / fromAmount!).toDecimal(scaleOnInfinitePrecision: 12);

    //todo: check if print needed
    // debugPrint(
    //     "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");

    if (shouldNotifyListeners) {
      notifyListeners();
    }
  }

  void setOnError({
    required void Function(String)? onError,
    bool shouldNotifyListeners = false,
  }) {
    _onError = onError;
    if (shouldNotifyListeners) {
      notifyListeners();
    }
  }

  Future<void> swap({FixedRateMarket? market}) async {
    final Decimal? newToAmount = fromAmount;
    final Decimal? newFromAmount = toAmount;

    fromAmount = newFromAmount;
    toAmount = newToAmount;

    minAmount = null;
    maxAmount = null;

    if (exchangeType == ExchangeRateType.fixed &&
        exchange?.name == ChangeNowExchange.exchangeName) {
      await updateMarket(market, false);
    } else {
      final Currency? newTo = from;
      final Currency? newFrom = to;

      _to = newTo;
      _from = newFrom;

      await updateRanges(shouldNotifyListeners: false);

      await updateEstimate(
        shouldNotifyListeners: false,
        reversed: reversed,
      );
    }

    notifyListeners();
  }
}