import 'dart:async';

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

import '../core/network/dio_client.dart';
import '../data/models/finance/airdrop_eligibility.dart';
import '../data/models/finance/airdrop_record_item.dart';
import '../data/models/finance/staking_config.dart';
import '../data/models/finance/staking_wallet_balance.dart';
import '../data/services/spot_service.dart';
import '../data/services/staking_service.dart';
import 'auth_provider.dart';

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

class StakingState {
  final bool isLoadingConfig;
  final bool isRefreshingBalance;
  final bool isSubmitting;
  final bool isTransfering;
  final String? errorMessage;
  final List<StakingConfig> configs;
  final StakingConfig? selectedConfig;
  final String fundingAvailable;
  final StakingWalletBalance stakingWallet;
  final int teamNum;
  final String lockedTotal;
  final bool isLoadingOverview;

  const StakingState({
    this.isLoadingConfig = false,
    this.isRefreshingBalance = false,
    this.isSubmitting = false,
    this.isTransfering = false,
    this.errorMessage,
    this.configs = const [],
    this.selectedConfig,
    this.fundingAvailable = '0',
    this.stakingWallet = const StakingWalletBalance(
      coinUnit: '',
      availableBalance: '0',
      lockedBalance: '0',
    ),
    this.teamNum = 0,
    this.lockedTotal = '0',
    this.isLoadingOverview = false,
  });

  StakingState copyWith({
    bool? isLoadingConfig,
    bool? isRefreshingBalance,
    bool? isSubmitting,
    bool? isTransfering,
    String? errorMessage,
    List<StakingConfig>? configs,
    StakingConfig? selectedConfig,
    bool clearSelectedConfig = false,
    String? fundingAvailable,
    StakingWalletBalance? stakingWallet,
    int? teamNum,
    String? lockedTotal,
    bool? isLoadingOverview,
  }) {
    return StakingState(
      isLoadingConfig: isLoadingConfig ?? this.isLoadingConfig,
      isRefreshingBalance: isRefreshingBalance ?? this.isRefreshingBalance,
      isSubmitting: isSubmitting ?? this.isSubmitting,
      isTransfering: isTransfering ?? this.isTransfering,
      errorMessage: errorMessage,
      configs: configs ?? this.configs,
      selectedConfig:
          clearSelectedConfig ? null : (selectedConfig ?? this.selectedConfig),
      fundingAvailable: fundingAvailable ?? this.fundingAvailable,
      stakingWallet: stakingWallet ?? this.stakingWallet,
      teamNum: teamNum ?? this.teamNum,
      lockedTotal: lockedTotal ?? this.lockedTotal,
      isLoadingOverview: isLoadingOverview ?? this.isLoadingOverview,
    );
  }
}

class StakingNotifier extends AutoDisposeNotifier<StakingState> {
  StakingService get _stakingService =>
      StakingService(ref.read(dioClientProvider));
  SpotService get _spotService => SpotService(ref.read(dioClientProvider));

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

  Future<void> init({String? configId}) async {
    state = state.copyWith(isLoadingConfig: true, errorMessage: null);
    try {
      StakingConfig? selected;
      List<StakingConfig> list = const [];

      if (configId != null && configId.isNotEmpty) {
        list = await _stakingService.getConfigs();
        final id = int.tryParse(configId);
        if (id != null) {
          for (final item in list) {
            if (item.id == id) {
              selected = item;
              break;
            }
          }
        }
        selected ??= await _stakingService.getMinOpenConfig();
      } else {
        final results = await Future.wait([
          _stakingService.getConfigs(),
          _stakingService.getMinOpenConfig(),
        ]);
        list = results[0] as List<StakingConfig>;
        selected = results[1] as StakingConfig?;
        if (selected == null && list.isNotEmpty) {
          selected ??= list.first;
        }
      }

      if (selected == null) {
        state = state.copyWith(
          isLoadingConfig: false,
          configs: list,
          clearSelectedConfig: true,
          errorMessage: 'No staking config available',
        );
        return;
      }

      state = state.copyWith(
        configs: list,
        selectedConfig: selected,
        isLoadingConfig: false,
      );
      unawaited(_refreshWithdrawableFund());
      unawaited(refreshOverviewStats());
    } catch (e) {
      state = state.copyWith(isLoadingConfig: false, errorMessage: '$e');
    }
  }

  Future<void> refreshBalances() async {
    await Future.wait([
      _refreshWithdrawableFund(),
      refreshOverviewStats(),
    ]);
  }

  /// 对齐 Web `refreshOverviewStats`：团队人数 + 锁仓总量
  Future<void> refreshOverviewStats() async {
    final config = state.selectedConfig;
    final rawUnit = config?.coinUnit.trim() ?? '';
    final coinUnit =
        rawUnit.isEmpty ? StakingService.stakingCoinUnit : rawUnit;
    state = state.copyWith(isLoadingOverview: true);
    try {
      final team = await _stakingService.getTeamOverview();
      var locked = team.lockTotal;
      if (locked <= 0) {
        try {
          final wallet = await _stakingService.getStakingWalletBalance(coinUnit);
          final available = double.tryParse(wallet.availableBalance) ?? 0;
          final lockedBal = double.tryParse(wallet.lockedBalance) ?? 0;
          locked = available + lockedBal;
        } catch (_) {
          locked = 0;
        }
      }
      state = state.copyWith(
        teamNum: team.teamNum,
        lockedTotal: '$locked',
        isLoadingOverview: false,
      );
    } catch (_) {
      try {
        final wallet = await _stakingService.getStakingWalletBalance(coinUnit);
        final available = double.tryParse(wallet.availableBalance) ?? 0;
        final lockedBal = double.tryParse(wallet.lockedBalance) ?? 0;
        state = state.copyWith(
          lockedTotal: '${available + lockedBal}',
          isLoadingOverview: false,
        );
      } catch (__) {
        state = state.copyWith(
          teamNum: 0,
          lockedTotal: '0',
          isLoadingOverview: false,
        );
      }
    }
  }

  Future<void> selectConfig(StakingConfig config) async {
    state = state.copyWith(selectedConfig: config, errorMessage: null);
    await _refreshWithdrawableFund();
  }

  /// 对齐 Web `refreshWithdrawableIbit`：仅拉可提现账户余额
  Future<void> _refreshWithdrawableFund() async {
    final config = state.selectedConfig;
    final rawUnit = config?.coinUnit.trim() ?? '';
    final coinUnit = rawUnit.isEmpty ? StakingService.stakingCoinUnit : rawUnit;
    state = state.copyWith(isRefreshingBalance: true);
    try {
      final available =
          await _stakingService.getFundingWalletAvailable(coinUnit);
      state = state.copyWith(
        fundingAvailable: available,
        isRefreshingBalance: false,
      );
    } catch (_) {
      state = state.copyWith(
        fundingAvailable: '0',
        isRefreshingBalance: false,
      );
    }
  }

  String? validateAmount(String amount) {
    final config = state.selectedConfig;
    if (config == null) return 'errServiceUnavailable';
    final input = _d(amount);
    if (input <= Decimal.zero) return 'errEnterAmount';
    final minAmount = _d(config.minAmount);
    if (input < minAmount) return 'errAmountBelowMin:${config.minAmount}';
    final maxAmount = _d(config.maxAmount);
    if (maxAmount > Decimal.zero && input > maxAmount) {
      return 'errAmountAboveMax:${config.maxAmount}';
    }
    final available = _d(state.fundingAvailable);
    if (input > available) return 'errExceedBalance';
    return null;
  }

  Future<String?> submitStake(String amount) async {
    final config = state.selectedConfig;
    if (config == null) return 'errServiceUnavailable';
    if (!ref.read(isLoggedInProvider)) return 'errNeedLogin';
    final validation = validateAmount(amount);
    if (validation != null) return validation;
    state = state.copyWith(isSubmitting: true, errorMessage: null);
    try {
      await _stakingService.submitStake(configId: config.id, amount: amount);
      await Future.wait([
        _refreshWithdrawableFund(),
        refreshOverviewStats(),
      ]);
      state = state.copyWith(isSubmitting: false);
      return null;
    } catch (e) {
      state = state.copyWith(isSubmitting: false, errorMessage: '$e');
      return '$e';
    }
  }

  Future<String?> transfer({
    required int direction,
    required String amount,
  }) async {
    final config = state.selectedConfig;
    if (config == null) return 'errServiceUnavailable';
    if (!ref.read(isLoggedInProvider)) return 'errNeedLogin';
    final val = _d(amount);
    if (val <= Decimal.zero) return 'errEnterAmount';
    state = state.copyWith(isTransfering: true, errorMessage: null);
    try {
      await _spotService.transfer(
        symbol: config.coinUnit,
        amount: val.toDouble(),
        direction: direction,
      );
      await _refreshWithdrawableFund();
      state = state.copyWith(isTransfering: false);
      return null;
    } catch (e) {
      state = state.copyWith(isTransfering: false, errorMessage: '$e');
      return '$e';
    }
  }
}

class AirdropState {
  final bool isLoadingEligibility;
  final bool isLoadingRecords;
  final bool isClaiming;
  final bool isLoadingMore;
  final String? errorMessage;
  final AirdropEligibility eligibility;
  final List<AirdropRecordItem> records;
  final int pageNo;
  final int totalPages;

  const AirdropState({
    this.isLoadingEligibility = false,
    this.isLoadingRecords = false,
    this.isClaiming = false,
    this.isLoadingMore = false,
    this.errorMessage,
    this.eligibility = const AirdropEligibility(
      inviteCount: 0,
      requiredInviteCount: 3,
      inviteTaskCompleted: false,
      hasActiveStaking: false,
      hasPendingAirdrop: false,
      claimableAmount: '0',
      eligible: false,
      message: '',
    ),
    this.records = const [],
    this.pageNo = 1,
    this.totalPages = 0,
  });

  bool get hasMore => totalPages > pageNo;

  AirdropState copyWith({
    bool? isLoadingEligibility,
    bool? isLoadingRecords,
    bool? isClaiming,
    bool? isLoadingMore,
    String? errorMessage,
    AirdropEligibility? eligibility,
    List<AirdropRecordItem>? records,
    int? pageNo,
    int? totalPages,
  }) {
    return AirdropState(
      isLoadingEligibility: isLoadingEligibility ?? this.isLoadingEligibility,
      isLoadingRecords: isLoadingRecords ?? this.isLoadingRecords,
      isClaiming: isClaiming ?? this.isClaiming,
      isLoadingMore: isLoadingMore ?? this.isLoadingMore,
      errorMessage: errorMessage,
      eligibility: eligibility ?? this.eligibility,
      records: records ?? this.records,
      pageNo: pageNo ?? this.pageNo,
      totalPages: totalPages ?? this.totalPages,
    );
  }
}

class AirdropNotifier extends AutoDisposeNotifier<AirdropState> {
  StakingService get _stakingService =>
      StakingService(ref.read(dioClientProvider));

  void _log(String message) {
    if (kDebugMode) {
      debugPrint('[Airdrop][Provider] $message');
    }
  }

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

  Future<void> init() async {
    final isLoggedIn = ref.read(isLoggedInProvider);
    _log('init start isLoggedIn=$isLoggedIn');
    state = state.copyWith(
      isLoadingEligibility: isLoggedIn,
      isLoadingRecords: isLoggedIn,
      errorMessage: null,
    );
    if (!isLoggedIn) {
      state = state.copyWith(
        eligibility: AirdropEligibility.empty(),
        records: const [],
        pageNo: 1,
        totalPages: 0,
        isLoadingEligibility: false,
        isLoadingRecords: false,
      );
      _log('init stop for guest user');
      return;
    }
    // 并行请求，收集结果后一次性更新 state，避免多次 rebuild
    AirdropEligibility? eligibilityResult;
    AirdropRecordPage? recordResult;
    String? loadError;

    await Future.wait([
      _stakingService.getAirdropEligibility().then(
        (e) {
          eligibilityResult = e;
        },
      ).catchError((e, st) {
        loadError = '$e';
        _log('eligibility request failed: $e');
        _log('$st');
      }),
      _stakingService.getAirdropRecords(pageNo: 1, pageSize: 10).then(
        (p) {
          recordResult = p;
        },
      ).catchError((e, st) {
        loadError ??= '$e';
        _log('records request failed: $e');
        _log('$st');
      }),
    ]);

    state = state.copyWith(
      eligibility: eligibilityResult ?? AirdropEligibility.empty(),
      records: recordResult?.content ?? const [],
      pageNo: recordResult?.pageNo ?? 1,
      totalPages: recordResult?.totalPages ?? 0,
      isLoadingEligibility: false,
      isLoadingRecords: false,
      errorMessage: loadError,
    );
    _log(
      'init done eligible=${state.eligibility.eligible} records=${state.records.length} pageNo=${state.pageNo} totalPages=${state.totalPages} error=${state.errorMessage}',
    );
  }

  Future<String?> claim() async {
    if (!ref.read(isLoggedInProvider)) return 'errNeedLogin';
    if (!state.eligibility.eligible) return state.eligibility.message;
    state = state.copyWith(isClaiming: true, errorMessage: null);
    try {
      await _stakingService.claimAirdrop();
      await init();
      state = state.copyWith(isClaiming: false);
      return null;
    } catch (e) {
      state = state.copyWith(isClaiming: false, errorMessage: '$e');
      return '$e';
    }
  }

  Future<void> loadMore() async {
    if (state.isLoadingMore ||
        state.isLoadingRecords ||
        !state.hasMore ||
        !ref.read(isLoggedInProvider)) {
      return;
    }
    state = state.copyWith(isLoadingMore: true);
    _log(
        'loadMore start currentPage=${state.pageNo} totalPages=${state.totalPages}');
    try {
      final nextPageNo = state.pageNo + 1;
      final page = await _stakingService.getAirdropRecords(
        pageNo: nextPageNo,
        pageSize: 10,
      );
      state = state.copyWith(
        isLoadingMore: false,
        records: [...state.records, ...page.content],
        pageNo: page.pageNo,
        totalPages: page.totalPages,
      );
      _log(
        'loadMore success nextPage=${page.pageNo} append=${page.content.length} totalRecords=${state.records.length}',
      );
    } catch (e) {
      state = state.copyWith(isLoadingMore: false, errorMessage: '$e');
      _log('loadMore failed: $e');
    }
  }
}

final stakingProvider =
    AutoDisposeNotifierProvider<StakingNotifier, StakingState>(
  StakingNotifier.new,
);

final airdropProvider =
    AutoDisposeNotifierProvider<AirdropNotifier, AirdropState>(
  AirdropNotifier.new,
);
