import 'package:decimal/decimal.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../core/network/dio_client.dart';
import '../core/utils/dialog_utils.dart';
import '../core/utils/spot_transfer_asset.dart';
import '../core/utils/transfer_pair.dart';
import '../data/models/asset/transfer_record.dart';
import '../data/models/asset/today_pnl.dart';
import '../data/services/asset_service.dart';
import '../data/services/spot_service.dart';

class TransferInitOptions {
  const TransferInitOptions({
    this.from,
    this.to,
    this.defaultSymbol = 'USDT',
    this.preferDefaultSymbol = false,
    this.spotTradingBridgeOnly = false,
  });

  final String? from;
  final String? to;
  final String defaultSymbol;
  final bool preferDefaultSymbol;
  final bool spotTradingBridgeOnly;
}

class TransferState {
  final String fromType;
  final String toType;
  final bool spotTradingBridgeOnly;
  final bool preferDefaultSymbol;
  final List<String> openCoins;
  final Map<String, String> fundAvailable;
  final Map<String, String> spotAvailable;
  final Map<String, String> legacyBalances;
  final String spotTradingUsdtBalance;
  final String selectedCoin;
  final bool isLoading;
  final bool isSubmitting;
  final bool isLoadingCoins;
  final String? errorMessage;

  const TransferState({
    this.fromType = kWalletSpot,
    this.toType = kWalletSwap,
    this.spotTradingBridgeOnly = false,
    this.preferDefaultSymbol = false,
    this.openCoins = const ['USDT'],
    this.fundAvailable = const {},
    this.spotAvailable = const {},
    this.legacyBalances = const {},
    this.spotTradingUsdtBalance = '0',
    this.selectedCoin = 'USDT',
    this.isLoading = false,
    this.isSubmitting = false,
    this.isLoadingCoins = false,
    this.errorMessage,
  });

  bool get isSpotCoinTransfer =>
      involvesSpotTradingBridge(fromType, toType);

  String get displayCoinUnit => isSpotCoinTransfer ? selectedCoin : 'USDT';

  List<String> get transferOtherTypes {
    if (spotTradingBridgeOnly) {
      return const [kWalletSpotTrading];
    }
    return kOtherWalletTypes;
  }

  Decimal get fromBalance {
    if (isSpotCoinTransfer) {
      if (fromType == kWalletSpotTrading) {
        return _d(spotAvailable[selectedCoin.toUpperCase()] ?? '0');
      }
      if (fromType == kWalletSpot) {
        return _d(fundAvailable[selectedCoin.toUpperCase()] ?? '0');
      }
    }
    if (fromType == kWalletSpotTrading) {
      return _d(spotTradingUsdtBalance);
    }
    return _d(legacyBalances[fromType] ?? '0');
  }

  TransferState copyWith({
    String? fromType,
    String? toType,
    bool? spotTradingBridgeOnly,
    bool? preferDefaultSymbol,
    List<String>? openCoins,
    Map<String, String>? fundAvailable,
    Map<String, String>? spotAvailable,
    Map<String, String>? legacyBalances,
    String? spotTradingUsdtBalance,
    String? selectedCoin,
    bool? isLoading,
    bool? isSubmitting,
    bool? isLoadingCoins,
    String? errorMessage,
  }) {
    return TransferState(
      fromType: fromType ?? this.fromType,
      toType: toType ?? this.toType,
      spotTradingBridgeOnly:
          spotTradingBridgeOnly ?? this.spotTradingBridgeOnly,
      preferDefaultSymbol: preferDefaultSymbol ?? this.preferDefaultSymbol,
      openCoins: openCoins ?? this.openCoins,
      fundAvailable: fundAvailable ?? this.fundAvailable,
      spotAvailable: spotAvailable ?? this.spotAvailable,
      legacyBalances: legacyBalances ?? this.legacyBalances,
      spotTradingUsdtBalance:
          spotTradingUsdtBalance ?? this.spotTradingUsdtBalance,
      selectedCoin: selectedCoin ?? this.selectedCoin,
      isLoading: isLoading ?? this.isLoading,
      isSubmitting: isSubmitting ?? this.isSubmitting,
      isLoadingCoins: isLoadingCoins ?? this.isLoadingCoins,
      errorMessage: errorMessage,
    );
  }
}

Decimal _d(String v) => Decimal.tryParse(v) ?? Decimal.zero;

class TransferNotifier extends AutoDisposeNotifier<TransferState> {
  TransferInitOptions _options = const TransferInitOptions();
  bool _initialized = false;

  @override
  TransferState build() => const TransferState();

  Future<void> init(TransferInitOptions options) async {
    _options = options;
    final pair = options.spotTradingBridgeOnly
        ? normalizeTransferPair(
            options.from ?? kWalletSpot,
            options.to ?? kWalletSpotTrading,
          )
        : normalizeTransferPair(
            options.from ?? kWalletSpot,
            options.to ?? kWalletSwap,
          );
    state = state.copyWith(
      fromType: pair.$1,
      toType: pair.$2,
      spotTradingBridgeOnly: options.spotTradingBridgeOnly,
      preferDefaultSymbol: options.preferDefaultSymbol,
      selectedCoin: options.defaultSymbol.toUpperCase(),
      isLoading: true,
      errorMessage: null,
    );
    await _loadLegacyBalances();
    if (state.isSpotCoinTransfer || options.spotTradingBridgeOnly) {
      await _loadSpotTransferAssets();
    }
    state = state.copyWith(isLoading: false);
    _initialized = true;
  }

  Future<void> _loadLegacyBalances() async {
    try {
      final dio = ref.read(dioClientProvider);
      final assetService = AssetService(dio);
      final spotService = SpotService(dio);
      final results = await Future.wait([
        assetService.getTodayPnl(),
        spotService.getAssets().catchError((_) => <String, dynamic>{}),
      ]);
      final todayPnl = results[0] as TodayPnl;
      final spotData = results[1] as Map<String, dynamic>;
      state = state.copyWith(
        legacyBalances: _legacyBalancesFromTodayPnl(todayPnl),
        spotTradingUsdtBalance:
            parseSpotAvailableMap(spotData)['USDT'] ?? '0',
      );
    } catch (e) {
      state = state.copyWith(errorMessage: e.toString());
    }
  }

  Map<String, String> _legacyBalancesFromTodayPnl(TodayPnl todayPnl) {
    final list = todayPnl.accountInfoList;
    String balAt(int idx) {
      if (idx < 0 || idx >= list.length) {
        return '0';
      }
      return list[idx].balance.toString();
    }
    return {
      kWalletSwap: balAt(kSwapAccountIdx),
      kWalletFollow: balAt(kFollowAccountIdx),
      kWalletSpot: balAt(kSpotAccountIdx),
    };
  }

  Future<void> _loadSpotTransferAssets() async {
    state = state.copyWith(isLoadingCoins: true);
    try {
      final dio = ref.read(dioClientProvider);
      final spotService = SpotService(dio);
      final results = await Future.wait([
        spotService.getCoins().catchError((_) => <Map<String, dynamic>>[]),
        spotService.getAssets().catchError((_) => <String, dynamic>{}),
        dio
            .post<Map<String, dynamic>>('uc/asset/wallet', data: {})
            .then((r) => r.data)
            .catchError((_) => null),
      ]);
      final coinsRaw = results[0] as List<Map<String, dynamic>>;
      final spotData = results[1] as Map<String, dynamic>;
      final fundRaw = results[2];
      var openCoins = parseOpenCoinSymbols(coinsRaw);
      var selected = state.selectedCoin.toUpperCase();
      if (_options.preferDefaultSymbol &&
          openCoins.contains(_options.defaultSymbol.toUpperCase())) {
        selected = _options.defaultSymbol.toUpperCase();
      } else if (!openCoins.contains(selected)) {
        selected = openCoins.contains('USDT') ? 'USDT' : openCoins.first;
      }
      state = state.copyWith(
        openCoins: openCoins,
        selectedCoin: selected,
        spotAvailable: parseSpotAvailableMap(spotData),
        fundAvailable: parseFundWalletBalances(fundRaw),
        spotTradingUsdtBalance:
            parseSpotAvailableMap(spotData)['USDT'] ?? state.spotTradingUsdtBalance,
        isLoadingCoins: false,
      );
    } catch (e) {
      state = state.copyWith(isLoadingCoins: false, errorMessage: e.toString());
    }
  }

  Future<void> refresh() async {
    if (!_initialized) {
      return;
    }
    await _loadLegacyBalances();
    if (state.isSpotCoinTransfer || state.spotTradingBridgeOnly) {
      await _loadSpotTransferAssets();
    }
  }

  void selectCoin(String coin) {
    state = state.copyWith(selectedCoin: coin.toUpperCase());
  }

  void setFromType(String type) {
    final pair = normalizeTransferPair(type, state.toType);
    state = state.copyWith(fromType: pair.$1, toType: pair.$2);
    _maybeLoadSpotAssets();
  }

  void setToType(String type) {
    final pair = normalizeTransferPair(state.fromType, type);
    state = state.copyWith(fromType: pair.$1, toType: pair.$2);
    _maybeLoadSpotAssets();
  }

  void _maybeLoadSpotAssets() {
    if (state.isSpotCoinTransfer) {
      Future.microtask(_loadSpotTransferAssets);
    }
  }

  void swapAccounts() {
    if (state.spotTradingBridgeOnly) {
      final nextFrom =
          state.fromType == kWalletSpot ? kWalletSpotTrading : kWalletSpot;
      final nextTo =
          nextFrom == kWalletSpot ? kWalletSpotTrading : kWalletSpot;
      state = state.copyWith(fromType: nextFrom, toType: nextTo);
      Future.microtask(_loadSpotTransferAssets);
      return;
    }
    final pair = normalizeTransferPair(state.toType, state.fromType);
    state = state.copyWith(fromType: pair.$1, toType: pair.$2);
    _maybeLoadSpotAssets();
  }

  Future<bool> submit(String amount) async {
    final pair = normalizeTransferPair(state.fromType, state.toType);
    state = state.copyWith(fromType: pair.$1, toType: pair.$2);
    if (pair.$1 == pair.$2) {
      state = state.copyWith(errorMessage: 'errSameAccount');
      return false;
    }
    final input = _d(amount);
    if (input <= Decimal.zero) {
      state = state.copyWith(errorMessage: 'errEnterAmount');
      return false;
    }
    if (input > state.fromBalance) {
      state = state.copyWith(errorMessage: 'errExceedBalance');
      return false;
    }

    state = state.copyWith(isSubmitting: true, errorMessage: null);
    try {
      final dio = ref.read(dioClientProvider);
      final fromWallet = state.fromType;
      final toWallet = state.toType;
      if (state.spotTradingBridgeOnly ||
          fromWallet == kWalletSpotTrading ||
          toWallet == kWalletSpotTrading) {
        final direction = fromWallet == kWalletSpot ? 1 : 2;
        await SpotService(dio).transfer(
          symbol: state.selectedCoin,
          amount: input.toDouble(),
          direction: direction,
        );
      } else {
        await AssetService(dio).transfer(
          amount: amount,
          from: fromWallet,
          to: toWallet,
        );
      }
      state = state.copyWith(isSubmitting: false);
      await refresh();
      return true;
    } catch (e) {
      state = state.copyWith(
        isSubmitting: false,
        errorMessage: extractErrorMessage(e),
      );
      return false;
    }
  }
}

final transferProvider =
    AutoDisposeNotifierProvider<TransferNotifier, TransferState>(
  TransferNotifier.new,
);

class TransferHistoryState {
  final List<TransferRecord> records;
  final bool isLoading;
  final bool hasMore;
  final int currentPage;
  final String? errorMessage;

  const TransferHistoryState({
    this.records = const [],
    this.isLoading = false,
    this.hasMore = true,
    this.currentPage = 1,
    this.errorMessage,
  });

  TransferHistoryState copyWith({
    List<TransferRecord>? records,
    bool? isLoading,
    bool? hasMore,
    int? currentPage,
    String? errorMessage,
  }) =>
      TransferHistoryState(
        records: records ?? this.records,
        isLoading: isLoading ?? this.isLoading,
        hasMore: hasMore ?? this.hasMore,
        currentPage: currentPage ?? this.currentPage,
        errorMessage: errorMessage,
      );
}

class TransferHistoryNotifier
    extends AutoDisposeNotifier<TransferHistoryState> {
  static const _pageSize = 10;

  @override
  TransferHistoryState build() {
    Future.microtask(_loadFirst);
    return const TransferHistoryState(isLoading: true);
  }

  Future<void> _loadFirst() async {
    state = state.copyWith(isLoading: true, errorMessage: null);
    try {
      final dio = ref.read(dioClientProvider);
      final records = await AssetService(dio)
          .getTransferList(pageNo: 1, pageSize: _pageSize);
      state = state.copyWith(
        records: records,
        isLoading: false,
        currentPage: 1,
        hasMore: records.length >= _pageSize,
      );
    } catch (e) {
      state = state.copyWith(isLoading: false, errorMessage: e.toString());
    }
  }

  Future<void> refresh() => _loadFirst();

  Future<void> loadMore() async {
    if (state.isLoading || !state.hasMore) {
      return;
    }
    state = state.copyWith(isLoading: true);
    try {
      final nextPage = state.currentPage + 1;
      final dio = ref.read(dioClientProvider);
      final records = await AssetService(dio)
          .getTransferList(pageNo: nextPage, pageSize: _pageSize);
      state = state.copyWith(
        records: [...state.records, ...records],
        isLoading: false,
        currentPage: nextPage,
        hasMore: records.length >= _pageSize,
      );
    } catch (e) {
      state = state.copyWith(isLoading: false, errorMessage: e.toString());
    }
  }
}

final transferHistoryProvider =
    AutoDisposeNotifierProvider<TransferHistoryNotifier, TransferHistoryState>(
  TransferHistoryNotifier.new,
);
