import 'dart:async';

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

import '../core/network/dio_client.dart';
import '../core/network/spot_ws_client.dart';
import '../data/models/asset/today_pnl.dart';
import '../data/services/asset_service.dart';
import '../data/services/futures_service.dart';
import '../data/services/spot_service.dart';
import '../data/models/finance/staking_order_item.dart';
import '../data/models/finance/staking_wallet_balance.dart';
import '../data/services/staking_service.dart';
import '../core/utils/spot_transfer_asset.dart';
import 'auth_provider.dart';
import 'futures_provider.dart' show FuturesPosition, activeBottomTabProvider;
import 'spot_provider.dart' show SpotWalletAsset;
import 'spot_ws_provider.dart';

class AssetState {
  /// swap/wallet-new/get 返回的账户数据
  final TodayPnl? todayPnl;

  /// 合约持仓列表（GET swap/wallet-new/get-with-positions 的 currentPositionWithCutList）
  final List<FuturesPosition> positions;

  /// 现货账户 USDT 估值（HTTP）
  final double spotTradingTotal;
  final double spotTodayPnl;
  final double spotTodayPnlRate;
  final List<SpotWalletAsset> spotWallets;
  final List<SpotWalletAsset> fundWallets;

  /// 锁仓账户 iBit 冻结量（总览展示，对齐 Web STAKING_LOCKED）
  final String stakingOverviewLocked;

  /// 锁仓账户 iBit 可用量（总锁仓余额计算用）
  final String stakingOverviewAvailable;

  /// 锁仓订单列表（锁仓 Tab 展示）
  final List<StakingOrderItem> stakingOrders;
  final bool stakingOrdersLoading;
  final StakingOrderStatusFilter stakingOrderStatusFilter;
  final bool hideZeroBalanceInFundTab;
  final bool hideZeroBalanceInSpotTab;

  final bool obscureBalance;
  final bool isLoading;
  final String? errorMessage;

  const AssetState({
    this.todayPnl,
    this.positions = const [],
    this.spotTradingTotal = 0,
    this.spotTodayPnl = 0,
    this.spotTodayPnlRate = 0,
    this.spotWallets = const [],
    this.fundWallets = const [],
    this.stakingOverviewLocked = '0',
    this.stakingOverviewAvailable = '0',
    this.stakingOrders = const [],
    this.stakingOrdersLoading = false,
    this.stakingOrderStatusFilter = StakingOrderStatusFilter.all,
    this.hideZeroBalanceInFundTab = false,
    this.hideZeroBalanceInSpotTab = false,
    this.obscureBalance = false,
    this.isLoading = false,
    this.errorMessage,
  });

  Decimal get totalAssetDecimal {
    final list = todayPnl?.accountInfoList;
    final base = list == null || list.isEmpty
        ? Decimal.zero
        : list.fold(Decimal.zero, (sum, a) => sum + a.currentCapital);
    final spotDecimal =
        Decimal.tryParse(spotTradingTotal.toString()) ?? Decimal.zero;
    return base + spotDecimal;
  }

  double get totalUsdtValue => totalAssetDecimal.toDouble();

  /// 按账户名称取 currentCapital
  /// SWAP → 永续合约资产, FOLLOW → 跟单账户资产, SPOT → 资金账户资产
  /// 按固定索引取账户资产：SWAP=0, FOLLOW=1, SPOT=2
  /// 服务端 accountInfoList 顺序固定，name 字段随 lang 变化，不可用于匹配
  int _walletIndex(String walletType) => switch (walletType) {
        'SWAP' => 0,
        'FOLLOW' => 1,
        'SPOT' => 2,
        _ => -1,
      };

  Decimal walletBalance(String walletType) {
    final list = todayPnl?.accountInfoList;
    if (list == null) return Decimal.zero;
    final i = _walletIndex(walletType);
    if (i < 0 || i >= list.length) return Decimal.zero;
    return list[i].currentCapital;
  }

  /// 获取账户的钱包余额
  Decimal accountBalance(String walletType) {
    final list = todayPnl?.accountInfoList;
    if (list == null) return Decimal.zero;
    final i = _walletIndex(walletType);
    if (i < 0 || i >= list.length) return Decimal.zero;
    return list[i].balance;
  }

  /// 获取账户的未实现盈亏（从持仓累加，不含体验金仓位）
  Decimal unrealizedPnl(String walletType) {
    // 仅合约账户有持仓数据
    if (walletType != 'SWAP') return Decimal.zero;
    if (positions.isEmpty) return Decimal.zero;
    return positions.where((p) => p.marginMode != '体验金').fold(Decimal.zero,
        (sum, p) {
      final pnl = Decimal.tryParse(p.unrealizedPnl.toString()) ?? Decimal.zero;
      return sum + pnl;
    });
  }

  /// 合约账户净值（不含体验金）
  /// 后端 getCurrentRevenue() 已过滤体验金浮盈亏，currentCapital 本身已正确，直接返回即可
  Decimal walletBalanceExcludeEG(String walletType) =>
      walletBalance(walletType);

  AssetState copyWith({
    TodayPnl? todayPnl,
    List<FuturesPosition>? positions,
    double? spotTradingTotal,
    double? spotTodayPnl,
    double? spotTodayPnlRate,
    List<SpotWalletAsset>? spotWallets,
    List<SpotWalletAsset>? fundWallets,
    String? stakingOverviewLocked,
    String? stakingOverviewAvailable,
    List<StakingOrderItem>? stakingOrders,
    bool? stakingOrdersLoading,
    StakingOrderStatusFilter? stakingOrderStatusFilter,
    bool? hideZeroBalanceInFundTab,
    bool? hideZeroBalanceInSpotTab,
    bool? obscureBalance,
    bool? isLoading,
    String? errorMessage,
  }) =>
      AssetState(
        todayPnl: todayPnl ?? this.todayPnl,
        positions: positions ?? this.positions,
        spotTradingTotal: spotTradingTotal ?? this.spotTradingTotal,
        spotTodayPnl: spotTodayPnl ?? this.spotTodayPnl,
        spotTodayPnlRate: spotTodayPnlRate ?? this.spotTodayPnlRate,
        spotWallets: spotWallets ?? this.spotWallets,
        fundWallets: fundWallets ?? this.fundWallets,
        stakingOverviewLocked:
            stakingOverviewLocked ?? this.stakingOverviewLocked,
        stakingOverviewAvailable:
            stakingOverviewAvailable ?? this.stakingOverviewAvailable,
        stakingOrders: stakingOrders ?? this.stakingOrders,
        stakingOrdersLoading:
            stakingOrdersLoading ?? this.stakingOrdersLoading,
        stakingOrderStatusFilter:
            stakingOrderStatusFilter ?? this.stakingOrderStatusFilter,
        hideZeroBalanceInFundTab:
            hideZeroBalanceInFundTab ?? this.hideZeroBalanceInFundTab,
        hideZeroBalanceInSpotTab:
            hideZeroBalanceInSpotTab ?? this.hideZeroBalanceInSpotTab,
        obscureBalance: obscureBalance ?? this.obscureBalance,
        isLoading: isLoading ?? this.isLoading,
        errorMessage: errorMessage,
      );
}

class AssetNotifier extends AutoDisposeNotifier<AssetState> {
  Timer? _pollTimer;
  StreamSubscription<Map<String, dynamic>>? _spotAssetWsSub;
  int _stakingOrdersRequestSeq = 0;

  static const int _stakingOrderPreviewSize = 5;

  bool _spotTradingTabHttpBootstrapped = false;
  bool _spotAssetChannelRetained = false;

  @override
  AssetState build() {
    // keepAlive 保证 provider 不因无 watcher 而销毁，同时 AutoDispose
    // 机制会在 ConsumerStatefulElement.deactivate() 时立即移除监听，
    // 避免 loadAssets async 回调命中已 defunct 的 element。
    ref.keepAlive();

    ref.onDispose(() {
      _pollTimer?.cancel();
      _teardownSpotAssetWs();
    });

    ref.listen<SpotWsClient>(spotWsClientProvider, (prev, next) {
      if (prev != null && !identical(prev, next)) {
        _spotAssetWsSub?.cancel();
        _spotAssetWsSub = null;
        _spotAssetChannelRetained = false;
        final onAssetPage = ref.read(activeBottomTabProvider) == 5;
        final onSpotSubTab = ref.read(currentAssetSubTabProvider) == 2;
        if (onAssetPage &&
            onSpotSubTab &&
            ref.read(isLoggedInProvider)) {
          Future.microtask(() => _ensureSpotAssetWs());
        }
      }
    });

    ref.listen<AsyncValue<SpotWsState>>(spotWsConnectionStateProvider,
        (prev, next) {
      final s = next.valueOrNull;
      if (s != SpotWsState.connected) return;
      if (!ref.read(isLoggedInProvider)) return;
      if (prev?.valueOrNull == SpotWsState.reconnecting) {
        Future.microtask(() => _loadSpotSliceFromHttp());
      }
    });

    ref.listen<bool>(isLoggedInProvider, (prev, loggedIn) {
      if (loggedIn) {
        _spotTradingTabHttpBootstrapped = false;
        Future.microtask(() {
          if (ref.read(activeBottomTabProvider) == 5 &&
              ref.read(currentAssetSubTabProvider) == 2) {
            onSpotTradingTabVisible();
          }
        });
      } else {
        _spotTradingTabHttpBootstrapped = false;
        _teardownSpotAssetWs();
      }
    });

    ref.listen<int>(currentAssetSubTabProvider, (prev, subTab) {
      if (ref.read(activeBottomTabProvider) != 5) return;
      if (subTab == 3) {
        startPositionPolling();
      } else {
        stopPositionPolling();
      }
      if (subTab == 2 && ref.read(isLoggedInProvider)) {
        Future.microtask(() => onSpotTradingTabVisible());
      } else if (prev == 2) {
        onSpotTradingTabHidden();
      }
      if (subTab == 5 && ref.read(isLoggedInProvider)) {
        Future.microtask(() => loadStakingOrders());
      }
    });

    ref.listen<int>(activeBottomTabProvider, (prev, tabIndex) {
      if (tabIndex != 5) {
        stopPositionPolling();
        onSpotTradingTabHidden();
      } else {
        final subTab = ref.read(currentAssetSubTabProvider);
        if (subTab == 3) {
          startPositionPolling();
        }
        if (subTab == 2 && ref.read(isLoggedInProvider)) {
          Future.microtask(() => onSpotTradingTabVisible());
        }
      }
    });

    Future.microtask(loadAssets);
    return const AssetState(isLoading: true);
  }

  Future<void> onSpotTradingTabVisible() async {
    if (!ref.read(isLoggedInProvider)) return;
    if (!_spotTradingTabHttpBootstrapped) {
      _spotTradingTabHttpBootstrapped = true;
      await _loadSpotSliceFromHttp();
    }
    _ensureSpotAssetWs();
  }

  void onSpotTradingTabHidden() => _teardownSpotAssetWs();

  Future<void> _loadSpotSliceFromHttp() async {
    if (!ref.read(isLoggedInProvider)) return;
    try {
      final dio = ref.read(dioClientProvider);
      final spotData = await SpotService(dio)
          .getAssets(hideZero: state.hideZeroBalanceInSpotTab);
      final spotTotal = _toDouble(spotData['totalAmount']);
      final spotPnl = _toDouble(spotData['todayPnl']);
      final spotPnlRate = _toDouble(spotData['todayPnlRate']);
      final spotWallets = _parseSpotWallets(spotData);
      state = state.copyWith(
        spotTradingTotal: spotTotal,
        spotTodayPnl: spotPnl,
        spotTodayPnlRate: spotPnlRate,
        spotWallets: spotWallets,
      );
    } catch (_) {}
  }

  Future<void> _loadFundSliceFromHttp() async {
    if (!ref.read(isLoggedInProvider)) return;
    try {
      final dio = ref.read(dioClientProvider);
      final fundData = await AssetService(dio)
          .getFundAssets(hideZero: state.hideZeroBalanceInFundTab);
      state = state.copyWith(fundWallets: _parseSpotWallets(fundData));
    } catch (_) {}
  }

  void _ensureSpotAssetWs() {
    if (!ref.read(isLoggedInProvider)) return;
    _spotAssetWsSub?.cancel();
    _spotAssetWsSub = null;
    final ws = ref.read(spotWsClientProvider);
    if (!_spotAssetChannelRetained) {
      ws.retainSpotAssetChannel();
      _spotAssetChannelRetained = true;
    }
    _spotAssetWsSub = ws.assetStream.listen(_onSpotAssetPush);
  }

  void _teardownSpotAssetWs() {
    _spotAssetWsSub?.cancel();
    _spotAssetWsSub = null;
    if (!_spotAssetChannelRetained) return;
    try {
      ref.read(spotWsClientProvider).releaseSpotAssetChannel();
    } catch (_) {}
    _spotAssetChannelRetained = false;
  }

  void _onSpotAssetPush(Map<String, dynamic> msg) {
    final list = msg['accountList'];
    if (list is! List) return;
    if (list.isEmpty) {
      Future.microtask(() => _loadSpotSliceFromHttp());
      return;
    }
    final merged = _mergeSpotWallets(state.spotWallets, list);
    state = state.copyWith(spotWallets: merged);
  }

  List<SpotWalletAsset> _mergeSpotWallets(
    List<SpotWalletAsset> current,
    List<dynamic> incoming,
  ) {
    final byCoin = <String, SpotWalletAsset>{
      for (final w in current) w.coin: w,
    };
    for (final raw in incoming) {
      if (raw is! Map) continue;
      final a = SpotWalletAsset.fromJson(Map<String, dynamic>.from(raw));
      if (a.coin.isEmpty) continue;
      byCoin[a.coin] = a;
    }
    final out = byCoin.values.toList()
      ..sort((a, b) => a.coin.compareTo(b.coin));
    return out;
  }

  void startPositionPolling() {
    _pollTimer?.cancel();
    _pollTimer = Timer.periodic(const Duration(seconds: 1), (_) {
      if (ref.read(activeBottomTabProvider) != 5) {
        stopPositionPolling();
        return;
      }
      silentRefresh();
    });
  }

  void stopPositionPolling() {
    _pollTimer?.cancel();
    _pollTimer = null;
  }

  Future<void> loadAssets({bool silent = false}) async {
    if (!silent) state = state.copyWith(isLoading: true, errorMessage: null);
    final dio = ref.read(dioClientProvider);
    try {
      final results = await Future.wait([
        AssetService(dio).getTodayPnl().catchError((_) => TodayPnl()),
        FuturesService(dio)
            .getWithPositions()
            .catchError((_) => <String, dynamic>{}),
        SpotService(dio)
            .getAssets(hideZero: state.hideZeroBalanceInSpotTab)
            .catchError((_) => <String, dynamic>{}),
        AssetService(dio)
            .getFundAssets(hideZero: state.hideZeroBalanceInFundTab)
            .catchError((_) => <String, dynamic>{}),
        StakingService(dio)
            .getStakingWalletBalance('IBIT')
            .catchError((_) => StakingWalletBalance.empty('IBIT')),
      ]);

      final todayPnl = results[0] as TodayPnl;
      final posData = results[1] as Map<String, dynamic>;
      final rawPositions =
          (posData['currentPositionWithCutList'] as List<dynamic>? ?? [])
              .cast<Map<String, dynamic>>();
      final positions = rawPositions
          .map((e) {
            try {
              return FuturesPosition.fromJson(e);
            } catch (_) {
              return null;
            }
          })
          .whereType<FuturesPosition>()
          .toList();

      final spotData = results[2] as Map<String, dynamic>;
      final spotTotal = _toDouble(spotData['totalAmount']);
      final spotPnl = _toDouble(spotData['todayPnl']);
      final spotPnlRate = _toDouble(spotData['todayPnlRate']);
      final rawAssetList = spotData['assetList'];
      final spotWallets = rawAssetList is List
          ? rawAssetList
              .whereType<Map<String, dynamic>>()
              .map(SpotWalletAsset.fromJson)
              .toList()
          : <SpotWalletAsset>[];

      final fundData = results[3] as Map<String, dynamic>;
      final fundWallets = _parseSpotWallets(fundData);

      final stakingWallet = results[4] as StakingWalletBalance;
      final accounts = todayPnl.accountInfoList;
      final walletAvailable = stakingOverviewAvailableFromAccounts(accounts);
      final walletLocked = stakingOverviewLockedFromAccounts(accounts);
      final merged = mergeStakingOverviewBalances(
        walletAvailable: walletAvailable,
        walletLocked: walletLocked,
        apiWallet: stakingWallet,
      );

      state = state.copyWith(
        todayPnl: todayPnl,
        positions: positions,
        spotTradingTotal: spotTotal,
        spotTodayPnl: spotPnl,
        spotTodayPnlRate: spotPnlRate,
        spotWallets: spotWallets,
        fundWallets: fundWallets,
        stakingOverviewLocked: merged.locked,
        stakingOverviewAvailable: merged.available,
        isLoading: false,
      );
    } catch (e) {
      if (!silent) {
        state = state.copyWith(isLoading: false, errorMessage: e.toString());
      }
    }
  }

  static double _toDouble(dynamic v) {
    if (v == null) return 0.0;
    if (v is num) return v.toDouble();
    return double.tryParse(v.toString()) ?? 0.0;
  }

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

  Future<void> silentRefresh() => loadAssets(silent: true);

  /// 锁仓 Tab 刷新：资产 + 订单
  Future<void> refreshStakingTab() async {
    await Future.wait([
      loadAssets(silent: true),
      loadStakingOrders(),
    ]);
  }

  Future<void> loadStakingOrders({bool clear = false}) async {
    if (!ref.read(isLoggedInProvider)) {
      return;
    }
    final requestSeq = ++_stakingOrdersRequestSeq;
    if (clear) {
      state = state.copyWith(stakingOrders: const [], stakingOrdersLoading: true);
    } else {
      state = state.copyWith(stakingOrdersLoading: true);
    }
    try {
      final dio = ref.read(dioClientProvider);
      final page = await StakingService(dio).getStakingOrders(
        pageNo: 1,
        pageSize: _stakingOrderPreviewSize,
        status: state.stakingOrderStatusFilter.apiStatus,
      );
      if (requestSeq != _stakingOrdersRequestSeq) {
        return;
      }
      state = state.copyWith(
        stakingOrders: page.content,
        stakingOrdersLoading: false,
      );
    } catch (_) {
      if (requestSeq != _stakingOrdersRequestSeq) {
        return;
      }
      state = state.copyWith(stakingOrders: const [], stakingOrdersLoading: false);
    }
  }

  void setStakingOrderStatusFilter(StakingOrderStatusFilter filter) {
    if (state.stakingOrderStatusFilter == filter) {
      return;
    }
    state = state.copyWith(stakingOrderStatusFilter: filter);
    Future.microtask(() => loadStakingOrders(clear: true));
  }

  void toggleObscure() =>
      state = state.copyWith(obscureBalance: !state.obscureBalance);

  void toggleHideZeroBalanceInFundTab() {
    state = state.copyWith(
      hideZeroBalanceInFundTab: !state.hideZeroBalanceInFundTab,
    );
    Future.microtask(_loadFundSliceFromHttp);
  }

  void toggleHideZeroBalanceInSpotTab() {
    state = state.copyWith(
      hideZeroBalanceInSpotTab: !state.hideZeroBalanceInSpotTab,
    );
    Future.microtask(_loadSpotSliceFromHttp);
  }

  static List<SpotWalletAsset> _parseSpotWallets(Map<String, dynamic> payload) {
    final rawAssetList = payload['assetList'];
    if (rawAssetList is! List) {
      return <SpotWalletAsset>[];
    }
    return rawAssetList
        .whereType<Map<String, dynamic>>()
        .map(SpotWalletAsset.fromJson)
        .toList();
  }
}

final assetProvider = AutoDisposeNotifierProvider<AssetNotifier, AssetState>(
  AssetNotifier.new,
);

/// 资产页子 tab：0 总览 1 资金 2 现货交易 3 合约 4 跟单 5 锁仓
final currentAssetSubTabProvider = StateProvider<int>((ref) => 0);
