import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:flutter/rendering.dart';
import 'package:guru_analytics_flutter/events_constants.dart';
import 'package:guru_app/account/account_data_store.dart';
import 'package:guru_app/api/data/orders/orders_model.dart';
import 'package:guru_app/api/guru_api.dart';
import 'package:guru_app/database/guru_db.dart';
import 'package:guru_app/financial/asset/assets_model.dart';
import 'package:guru_app/financial/asset/assets_store.dart';
import 'package:guru_app/financial/data/db/order_database.dart';
import 'package:guru_app/financial/iap/iap_model.dart';
import 'package:guru_app/financial/manifest/manifest.dart';
import 'package:guru_app/financial/manifest/manifest_manager.dart';
import 'package:guru_app/financial/product/product_model.dart';
import 'package:guru_app/financial/product/product_store.dart';
import 'package:guru_app/firebase/firebase.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/property_keys.dart';
import 'package:guru_app/property/settings/guru_settings.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/math/math_utils.dart';
import 'package:guru_utils/tuple/tuple.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';

/// Created by Haoyi on 2022/6/10
///

enum IapCause { success, error, canceled }

class IapManager {
  static final IapManager _instance = IapManager._();

  static IapManager get instance => _instance;

  static final ProductDetailsResponse _emptyResponse =
      ProductDetailsResponse(productDetails: [], notFoundIDs: [], error: null);

  final BehaviorSubject<Map<ProductId, ProductDetails>> _productDetailsSubject =
      BehaviorSubject.seeded({});
  final BehaviorSubject<AssetsStore<Asset>> _iapStoreSubject =
      BehaviorSubject.seeded(AssetsStore<Asset>.inactive());

  final Map<ProductId, IapRequest> iapRequestMap = HashMap<ProductId, IapRequest>();

  Stream<Map<ProductId, ProductDetails>> get observableProductDetails =>
      _productDetailsSubject.stream;

  Stream<AssetsStore<Asset>> get observableAssetStore => _iapStoreSubject.stream;

  Map<ProductId, ProductDetails> get loadedProductDetails => _productDetailsSubject.value;

  AssetsStore<Asset> get purchasedStore => _iapStoreSubject.value;

  final BehaviorSubject<bool> availableSubject = BehaviorSubject.seeded(false);

  Stream<bool> get observableAvailable => availableSubject.stream;

  bool get iapAvailable => availableSubject.value;

  final InAppPurchase _inAppPurchase;

  StreamSubscription? subscription;

  Timer? restorePointsTimer;

  IapManager._() : _inAppPurchase = InAppPurchase.instance;

  IapCause latestIapCause = IapCause.success;

  bool _restorePurchase = false;

  final iapRevenueAppEventOptions = AppEventOptions(
      capabilities:
          const AppEventCapabilities(AppEventCapabilities.firebase | AppEventCapabilities.guru),
      firebaseParamsConvertor: _iapRevenueToValue,
      guruParamsConvertor: _iapRevenueToValue);

  static Map<String, dynamic> _iapRevenueToValue(Map<String, dynamic> params) {
    final result = Map.of(params);
    final revenue = result.remove("revenue");
    if (revenue != null) {
      result["value"] = revenue;
    }
    return result;
  }

  void init() async {
    final iapCount = await AppProperty.getInstance().getIapCount();
    if (iapCount > 0) {
      GuruAnalytics.instance.setUserProperty("purchase_count", iapCount.toString());
      GuruAnalytics.instance.setUserProperty("is_iap_user", "true");
    } else {
      GuruAnalytics.instance.setUserProperty("is_iap_user", "false");
    }
    try {
      await reloadOrders();
    } catch (error, stacktrace) {
      Log.w("reloadOrders error! $error",
          stackTrace: stacktrace, syncCrashlytics: true, syncFirebase: true);
    }
    if (subscription == null) {
      final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
      subscription = purchaseUpdated.listen(
          (List<PurchaseDetails> purchaseDetailsList) {
            _listenToPurchaseUpdated(purchaseDetailsList);
          },
          onDone: () {},
          onError: (Object error) {
            // handle error here.
            Log.e("iap error:$error");
          });
      Log.i("iap service initialize completed");
    }
    Log.i("iap service initialized");

    _checkAndLoad();

    try {
      await AccountDataStore.instance.observableSaasUser
          .firstWhere((saasUser) => saasUser?.isValid == true);
      Future.delayed(const Duration(seconds: 5), () {
        reportFailedOrders();
      });
    } catch (error, stacktrace) {
      Log.w("wait account error! $error", stackTrace: stacktrace);
    } finally {}
  }

  Future switchSession() async {
    final iapCount = await AppProperty.getInstance().getIapCount();
    if (iapCount > 0) {
      GuruAnalytics.instance.setUserProperty("purchase_count", iapCount.toString());
      GuruAnalytics.instance.setUserProperty("is_iap_user", "true");
    } else {
      GuruAnalytics.instance.setUserProperty("is_iap_user", "false");
    }
    try {
      await reloadOrders();
    } catch (error, stacktrace) {
      Log.w("reloadOrders error! $error",
          stackTrace: stacktrace, syncCrashlytics: true, syncFirebase: true);
    }
    _checkAndLoad();
  }

  Future reloadOrders() async {
    final transactions = await GuruDB.instance.selectOrders(
        method: TransactionMethod.iap,
        attrs: [TransactionAttributes.asset, TransactionAttributes.subscriptions]);
    final newAssetStore = AssetsStore<Asset>();
    Log.d("reloadOrders ${transactions.length}");
    for (var transaction in transactions) {
      final productId = transaction.productId;
      Log.d(" ==> reloadOrder:${transaction.sku} $productId");
      newAssetStore.addAsset(Asset(productId, transaction));
    }
    _iapStoreSubject.addEx(newAssetStore);
  }

  void _checkAndLoad() async {
    var available = false;
    var retry = 0;
    Log.i("_checkAndLoad");
    do {
      final seconds = min(MathUtils.fibonacci(retry, offset: 2), 900);
      await Future.delayed(Duration(seconds: seconds));
      available = await _inAppPurchase.isAvailable().catchError((error, stacktrace) {
        Log.w("isAvailable error:$error", stackTrace: stacktrace);
        return false;
      });
      Log.d("_checkAndLoad:$retry available:$available");
      retry++;
    } while (!available);
    availableSubject.addIfChanged(true);
    try {
      await refreshProducts();
      if (GuruApp.instance.appSpec.deployment.autoRestoreIap ||
          GuruApp.instance.appSpec.productProfile.hasSubs()) {
        await restorePurchases();
      }
    } catch (error, stacktrace) {
      Log.w("restorePurchases error:$error",
          stackTrace: stacktrace, syncCrashlytics: true, syncFirebase: true);
    }
  }

  Future<bool> isAvailable() async {
    return await _inAppPurchase.isAvailable();
  }

  void _processIapError() async {
    latestIapCause = IapCause.error;
    for (var iapRequest in iapRequestMap.values) {
      iapRequest.response(false);
      final iapErrorMsg = "_processIapError:${iapRequest.productId}";
      Log.w(iapErrorMsg,
          error: PurchaseError(iapErrorMsg), syncFirebase: true, syncCrashlytics: true);
      try {
        await GuruDB.instance.upsertOrder(order: iapRequest.order.error(iapErrorMsg));
      } catch (error, stacktrace) {
        Log.w("_processIapError upsert error! $error", syncFirebase: true);
      }
    }
    iapRequestMap.clear();
  }

  void _processIapCancel() async {
    latestIapCause = IapCause.canceled;
    Log.d("_processIapCancel");
    for (var iapRequest in iapRequestMap.values) {
      final order = iapRequest.order;
      iapRequest.response(false);
      try {
        await GuruDB.instance.deleteOrder(order: order);
      } catch (error, stacktrace) {
        Log.w("_processIapCancel deleteOrder error! $error", syncFirebase: true);
      }
    }
    iapRequestMap.clear();
    // final iapErrorMsg = "_processIapCancel:$productId";
    // Log.w(iapErrorMsg,
    //     error: PurchaseError(iapErrorMsg), syncFirebase: true, syncCrashlytics: true);
  }

  // void _listenToPurchased() async {
  //   InAppPurchase.instance.purchaseStream.listen((purchaseDetailsList) {
  //     if (purchaseDetailsList.isEmpty) {
  //       return;
  //     }
  //     final subscriptionDetails = <ProductId, PurchaseDetails>{};
  //     for (var details in purchaseDetailsList) {
  //       Log.d(" details:${details.productID} ${details.status}");
  //       final productId =
  //           GuruApp.instance.findProductId(sku: details.productID) ?? ProductId.invalid;
  //       if (productId.isSubscription) {
  //         subscriptionDetails[productId] = details;
  //       }
  //     }
  //     if (Platform.isIOS) {
  //       checkSubscriptionForIos(subscriptionDetails);
  //     }
  //   });
  // }

  String dumpProductAndPurchased(ProductDetails details, PurchaseDetails purchaseDetails) {
    final StringBuffer sb = StringBuffer();

    if (Platform.isAndroid) {
      try {
        GooglePlayPurchaseDetails googlePlayDetails = purchaseDetails as GooglePlayPurchaseDetails;
        GooglePlayProductDetails googlePlayProduct = details as GooglePlayProductDetails;
        Log.d(
            "Android Product/Purchase ${buildGooglePlayDetailsString(googlePlayProduct, googlePlayDetails)}");
      } catch (error, stacktrace) {}
    } else if (Platform.isIOS) {
      AppStorePurchaseDetails appleDetails = purchaseDetails as AppStorePurchaseDetails;
      AppStoreProductDetails appleProduct = details as AppStoreProductDetails;
      sb.writeln("#### purchase ####");
      sb.writeln("productID: ${appleDetails.productID}");
      sb.writeln("purchaseID: ${appleDetails.purchaseID}");
      sb.writeln("transactionDate: ${appleDetails.transactionDate}");
      sb.writeln("verificationData: ${appleDetails.verificationData}");
      sb.writeln("status: ${appleDetails.status}");
      sb.writeln("skPaymentTransaction:");
      sb.writeln(
          "   =>originalTransaction:${appleDetails.skPaymentTransaction.originalTransaction}");
      sb.writeln("       =>${appleDetails.skPaymentTransaction.originalTransaction?.payment}");
      sb.writeln("   =>${appleDetails.skPaymentTransaction.transactionState}:");
      sb.writeln("   =>${appleDetails.skPaymentTransaction.transactionIdentifier}:");
      sb.writeln("\n#### product ####");
      sb.writeln("currencyCode: ${appleProduct.currencyCode}");
      sb.writeln("rawPrice: ${appleProduct.rawPrice}");
      sb.writeln("currencyCode: ${appleProduct.currencyCode}");
      sb.writeln("currencyCode skProduct");
      sb.writeln("    =>localizedTitle: ${appleProduct.skProduct.localizedTitle}");
      sb.writeln("    =>localizedDescription: ${appleProduct.skProduct.localizedDescription}");
      sb.writeln("    =>priceLocale: ${appleProduct.skProduct.priceLocale}");
      sb.writeln("    =>productIdentifier: ${appleProduct.skProduct.productIdentifier}");
      sb.writeln(
          "    =>subscriptionGroupIdentifier: ${appleProduct.skProduct.subscriptionGroupIdentifier}");
      sb.writeln("    =>appleProduct.skProduct.priceLocale");
      sb.writeln("        ->{appleProduct.skProduct.priceLocale}");

      Log.d("IOS Product/Purchase ${sb.toString()}");
    }
    return sb.toString();
  }

  static final monthRenewalDurations = [
    3 * DateTimeUtils.minuteInMillis,
    5 * DateTimeUtils.minuteInMillis,
    15 * DateTimeUtils.minuteInMillis,
    30 * DateTimeUtils.minuteInMillis,
    DateTimeUtils.hourInMillis
  ];

  static final weekRenewalDurations = [
    3 * DateTimeUtils.minuteInMillis,
    3 * DateTimeUtils.minuteInMillis,
    5 * DateTimeUtils.minuteInMillis,
    10 * DateTimeUtils.minuteInMillis,
    15 * DateTimeUtils.minuteInMillis
  ];

  int getIOSPeriodInterval(int numberOfUnits, SKSubscriptionPeriodUnit unit) {
    if (GuruSettings.instance.debugMode.get()) {
      final renewalSpeed =
          GuruApp.instance.appSpec.deployment.iosSandboxSubsRenewalSpeed.clamp(1, 5);
      switch (unit) {
        case SKSubscriptionPeriodUnit.day:
          return numberOfUnits * weekRenewalDurations[renewalSpeed - 1] ~/ 7;
        case SKSubscriptionPeriodUnit.week:
          return numberOfUnits * weekRenewalDurations[renewalSpeed - 1];
        case SKSubscriptionPeriodUnit.month:
          return numberOfUnits * monthRenewalDurations[renewalSpeed - 1];
        case SKSubscriptionPeriodUnit.year:
          return numberOfUnits * monthRenewalDurations[renewalSpeed - 1] * 12;
      }
    } else {
      switch (unit) {
        case SKSubscriptionPeriodUnit.day:
          return numberOfUnits * DateTimeUtils.dayInMillis;
        case SKSubscriptionPeriodUnit.week:
          return numberOfUnits * DateTimeUtils.weekInMillis;
        case SKSubscriptionPeriodUnit.month:
          return numberOfUnits * DateTimeUtils.dayInMillis * 31;
        case SKSubscriptionPeriodUnit.year:
          return numberOfUnits * DateTimeUtils.dayInMillis * 366;
      }
    }
  }

  bool checkSubscriptionPeriod(PurchaseDetails purchaseDetails, ProductDetails productDetails) {
    bool validOrder = false;
    if (Platform.isAndroid) {
      validOrder = true;
    } else if (Platform.isIOS) {
      final appleProduct = productDetails as AppStoreProductDetails;
      SKProductSubscriptionPeriodWrapper? period = appleProduct.skProduct.subscriptionPeriod;

      final appleDetails = purchaseDetails as AppStorePurchaseDetails;
      final paymentDiscount = appleDetails.skPaymentTransaction.payment.paymentDiscount;
      if (paymentDiscount != null) {
        Log.i("paymentDiscount: ${paymentDiscount.identifier} ${paymentDiscount.timestamp}");
        for (var discount in appleProduct.skProduct.discounts) {
          final discountSubPeriod = discount.subscriptionPeriod;
          Log.i(
              "check discount(${paymentDiscount.identifier}) product [ ${discount.identifier}  ${discountSubPeriod.unit} ${discountSubPeriod.numberOfUnits} ]");
          if (discount.identifier == paymentDiscount.identifier) {
            period = discount.subscriptionPeriod;
            break;
          }
        }
      }

      Log.i("checkSubscriptionPeriod: ${appleProduct.skProduct.productIdentifier} ${period?.unit} ${period?.numberOfUnits} ${appleDetails.transactionDate}");

      if (period != null) {
        final numberOfUnits = period.numberOfUnits;
        final unit = period.unit;
        final int validInterval = getIOSPeriodInterval(numberOfUnits, unit);
        final transactionTs = int.tryParse(purchaseDetails.transactionDate ?? "") ?? 0;
        final now = DateTimeUtils.currentTimeInMillis();
        final gracePeriod = GuruApp.instance.remoteDeployment.subscriptionGraceDays ??
            GuruApp.instance.appSpec.deployment.subscriptionGraceDays;
        /// 过期时间 = 订单的最后一次刷新时间 + 订阅周期(优惠周期) + 宽限期
        final expiredTs = transactionTs + validInterval + gracePeriod;
        /// 如果当前的时间小于过期时间，那么这个订单是有效的
        validOrder = now < expiredTs;
        Log.d(
            "productID: ${purchaseDetails.productID}) purchaseID: ${purchaseDetails.purchaseID}[$numberOfUnits][$unit] [$now < $transactionTs + $validInterval]($validOrder) ",
            tag: PropertyTags.iap);
      }
    }
    return validOrder;
  }

  Future processRestoredSubscription(List<PurchaseDetails> subscriptionPurchased) async {
    List<PurchaseDetails> purchasedDetails = subscriptionPurchased;
    if (Platform.isIOS) {
      purchasedDetails = buildLatestPurchasedPlanForIos(subscriptionPurchased);
    }
    final newPurchasedStore = purchasedStore.clone();
    final expiredSkus = <String>{};
    // 由于Android的订阅项目在失效后，这里将不会返回，因此需要判断这里的newPurchasedStore是否存在对应的purchased
    // 如果存在将会在后面进行处理，如果不存在。这里将会从purchasedStore中删除
    if (Platform.isAndroid) {
      final purchasedSkus = purchasedDetails.map((e) => e.productID).toSet();
      newPurchasedStore.removeWhere((productId, asset) {
        final expired = productId.isSubscription && !purchasedSkus.contains(productId.sku);
        Log.i("remove expired subscription[$productId] expired:$expired");
        if (expired) {
          expiredSkus.add(asset.productId.sku);
        }
        return expired;
      });
    }

    for (var purchased in purchasedDetails) {
      final productId = GuruApp.instance.findProductId(sku: purchased.productID);
      if (productId == null) {
        Log.w("productId is null! ${purchased.productID}");
        continue;
      }
      final productDetails = loadedProductDetails[productId];
      if (productDetails == null) {
        Log.w("product is null! ${purchased.productID}");
        continue;
      }
      final bool validPurchase = checkSubscriptionPeriod(purchased, productDetails);
      if (validPurchase) {
        Log.d(
            "[Restored Subscription] productID: ${purchased.productID}) purchaseID: ${purchased.purchaseID}",
            tag: PropertyTags.iap);
        final asset = newPurchasedStore.getAsset(productId);
        late OrderEntity newOrder;
        if (asset == null) {
          final product =
              await _createProduct(productId.createIntent(scene: "restore"), productDetails);
          newOrder = product.createOrder().success();
        } else {
          newOrder = asset.order.success();
        }
        try {
          await GuruDB.instance.replaceOrderBySku(order: newOrder);
        } catch (error, stacktrace) {
          Log.w("Failed to upsert order: $error $stacktrace", tag: PropertyTags.iap);
        }
        final newAsset = Asset(productId, newOrder);
        newPurchasedStore.addAsset(newAsset);
      } else {
        expiredSkus.add(productId.sku);
        Log.d(
            "Subscription is expired ${purchased.productID}) ${purchased.purchaseID} ${purchased.transactionDate}");
        // 这里暂不清newPurchasedStore，下次重进后该订阅信息会失效
      }
    }

    if (expiredSkus.isNotEmpty) {
      final graceCount = await AppProperty.getInstance().increaseGraceCount();
      Log.i("expired orders:${expiredSkus.length}} grace count: $graceCount");
      if (graceCount > GuruApp.instance.appSpec.deployment.subscriptionRestoreGraceCount) {
        try {
          await GuruDB.instance.deleteOrdersBySkus(expiredSkus);
        } catch (error, stacktrace) {
          Log.w("Failed to upsert order: $error $stacktrace", tag: PropertyTags.iap);
        }
        await AppProperty.getInstance().resetGraceCount();
        GuruAnalytics.instance.logGuruEvent('dev_iap_audit', {
          "item_category": "expired",
          "item_name": "sub",
          "platform": Platform.isAndroid ? "Android" : "iOS",
          "value": graceCount
        });
      }
    } else {
      await AppProperty.getInstance().resetGraceCount();
    }
    _iapStoreSubject.addEx(newPurchasedStore);
    Log.d("[RestoredSubscription] update purchasedStore ${newPurchasedStore.data.length}");
  }

  List<PurchaseDetails> buildLatestPurchasedPlanForIos(List<PurchaseDetails> purchaseDetails) {
    if (purchaseDetails.isEmpty) {
      return [];
    }
    final rawTransactionIds = purchaseDetails
        .map((details) => (details as AppStorePurchaseDetails)
            .skPaymentTransaction
            .originalTransaction
            ?.transactionIdentifier)
        .where((element) => element != null)
        .cast<String>()
        .toSet();
    Log.d("rawTransactionIds:$rawTransactionIds");
    final sortedPurchaseDetails = purchaseDetails.toList();
    sortedPurchaseDetails.sort((a, b) => (int.tryParse(b.transactionDate ?? '') ?? 0)
        .compareTo(int.tryParse(a.transactionDate ?? '') ?? 0));
    sortedPurchaseDetails.retainWhere((details) {
      var detail = details as AppStorePurchaseDetails;
      Log.d(
          "checkSubscriptionForIos ${detail.skPaymentTransaction.originalTransaction?.transactionIdentifier} ${detail.transactionDate} ${detail.skPaymentTransaction.transactionTimeStamp}");
      return rawTransactionIds
          .remove(detail.skPaymentTransaction.originalTransaction?.transactionIdentifier);
    });

    return sortedPurchaseDetails;
  }

  void checkSubscriptionForIos(List<PurchaseDetails> purchaseDetails) {
    if (purchaseDetails.isEmpty) {
      return;
    }
    final rawTransactionIds = purchaseDetails
        .map((details) => (details as AppStorePurchaseDetails)
            .skPaymentTransaction
            .originalTransaction
            ?.transactionIdentifier)
        .where((element) => element != null)
        .cast<String>()
        .toSet();
    Log.d("rawTransactionIds:$rawTransactionIds");
    final sortedPurchaseDetails = purchaseDetails.toList();
    sortedPurchaseDetails.sort((a, b) => (int.tryParse(b.transactionDate ?? '') ?? 0)
        .compareTo(int.tryParse(a.transactionDate ?? '') ?? 0));
    sortedPurchaseDetails.retainWhere((details) {
      var detail = details as AppStorePurchaseDetails;
      Log.d(
          "checkSubscriptionForIos ${detail.skPaymentTransaction.originalTransaction?.transactionIdentifier} ${detail.transactionDate} ${detail.skPaymentTransaction.transactionTimeStamp}");
      return rawTransactionIds
          .remove(detail.skPaymentTransaction.originalTransaction?.transactionIdentifier);
    });

    for (var details in sortedPurchaseDetails) {
      Log.d(
          "checkSubscriptionForIos ${details.productID} ${details.status} ${details.transactionDate}");
      final productId = GuruApp.instance.findProductId(sku: details.productID);
      final productDetails = loadedProductDetails[productId];
      if (productDetails != null) {
        dumpProductAndPurchased(productDetails, details);
      }
    }
  }

  void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
    final List<Tuple2<ProductId, PurchaseDetails>> restoredIapPurchases = [];
    final List<Tuple2<ProductId, PurchaseDetails>> pendingCompletePurchase = [];
    final List<PurchaseDetails> subscriptionPurchases = [];
    bool existsRestored = false;
    bool needRestore = false;
    Log.d("_listenToPurchaseUpdated ${purchaseDetailsList.length}");
    if (purchaseDetailsList.isEmpty) {
      if (_restorePurchase) {
        try {
          await processRestoredSubscription(subscriptionPurchases);
        } catch (error, stacktrace) {
          Log.w(
              "purchaseDetailsList is EMPTY! processRestoredSubscription error! $error $stacktrace",
              syncFirebase: true,
              syncCrashlytics: true);
        }
        _restorePurchase = false;
      }
      return;
    }
    for (var details in purchaseDetailsList) {
      final productId = GuruApp.instance.findProductId(sku: details.productID) ?? ProductId.invalid;
      Log.d(
          "[details]: $productId [${details.productID}] ${details.purchaseID} ${details.status} ${details.pendingCompletePurchase}");
      GuruAnalytics.instance.logGuruEvent('dev_iap_update', {
        "sku": details.productID,
        "orderId": details.purchaseID,
        "status": "${details.status.index}"
      });
      switch (details.status) {
        case PurchaseStatus.purchased:
          if (details.productID == "") {
            if (GuruApp.instance.productProfile.pointsIapIds.isNotEmpty) {
              Log.w(
                  "details.productID is empty And Exists PointsIap! ${details.purchaseID}! need restore");
              needRestore = true;
            } else {
              Log.w("details.productID is empty ${details.purchaseID}! ignore!!");
            }
            continue;
          }

          final productDetails = loadedProductDetails[productId];
          if (productDetails != null) {
            /// 如果是 IOS的 purchased订单，并且是订阅的订单，他又没在当前请求的列表中，证明他是一个恢复的订单
            if (Platform.isIOS && productId.isSubscription && !iapRequestMap.containsKey(productId)) {
              subscriptionPurchases.add(details);
              existsRestored = true;
            } else {
              await _completePurchase(productId, productDetails, details);
            }
          }

          Log.d("completePurchase ${details.productID} ${details.purchaseID}");
          break;
        case PurchaseStatus.restored:
          _restorePurchase = false;
          existsRestored = true;
          if (productId.isAsset) {
            restoredIapPurchases.add(Tuple2(productId, details));
            Log.d("restore possessive iap:$productId");
          } else if (productId.isSubscription) {
            Log.w("restore subscription product!", syncFirebase: true);
            subscriptionPurchases.add(details);
          }
          // 如果是未完成的商品或是恢复出了消耗品，都需要手动完成
          if (Platform.isAndroid) {
            final originPurchaseState =
                (details as GooglePlayPurchaseDetails).billingClientPurchase.purchaseState;
            Log.d(
                "restore android ${details.pendingCompletePurchase} $productId $originPurchaseState");
            if (originPurchaseState == PurchaseStateWrapper.purchased) {
              if (productId.isConsumable ||
                  (details.pendingCompletePurchase && productId.isAsset)) {
                Log.w("restore consumable product!", syncFirebase: true);
                pendingCompletePurchase.add(Tuple2(productId, details));
              }
            }
          } else {
            if (details.pendingCompletePurchase) {
              Log.d("restore ios pendingCompletePurchase: $productId");
              await _inAppPurchase.completePurchase(details);
            }
          }
          break;
        case PurchaseStatus.error:
          _processIapError();
          if (details.pendingCompletePurchase) {
            await _inAppPurchase.completePurchase(details);
          }
          break;
        case PurchaseStatus.canceled:
          _processIapCancel();
          if (details.pendingCompletePurchase) {
            await _inAppPurchase.completePurchase(details);
          }
          break;
        default:
          break;
      }
      Log.d(
          "_listenToPurchaseUpdated2:$productId [${details.productID}] ${details.purchaseID} ${details.status} ${details.pendingCompletePurchase}");
    }
    if (existsRestored) {
      if (pendingCompletePurchase.isNotEmpty) {
        await completeAllPurchases(pendingCompletePurchase);
        Log.d("manual complete/consume all purchases!", syncFirebase: true, syncCrashlytics: true);
      }

      if (restoredIapPurchases.isNotEmpty) {
        try {
          await processRestoredPurchases(restoredIapPurchases);
        } catch (error, stacktrace) {
          Log.w("processRestoredPurchases error! $error $stacktrace",
              syncFirebase: true, syncCrashlytics: true);
        }
      }
      try {
        await processRestoredSubscription(subscriptionPurchases);
      } catch (error, stacktrace) {
        Log.w("processRestoredSubscription error! $error $stacktrace",
            syncFirebase: true, syncCrashlytics: true);
      }
    }
    if (needRestore) {
      restorePointsTimer?.cancel();
      restorePointsTimer = Timer(const Duration(seconds: 1), () {
        restorePurchases();
      });
    }
  }

  Future processRestoredPurchases(
      List<Tuple2<ProductId, PurchaseDetails>> restoredIapPurchases) async {
    final newPurchased = purchasedStore.clone();
    final currentLoadedProductDetails = loadedProductDetails;
    final upsertOrders = <OrderEntity>[];
    for (var iapPurchased in restoredIapPurchases) {
      final productId = iapPurchased.item1;
      final asset = newPurchased.getAsset(iapPurchased.item1);
      final productDetails = currentLoadedProductDetails[productId];
      final order = asset?.order;
      // 证明是已经购买过的
      if (order != null) {
        // 如果没有购买成功，那么就重新创建一个
        if (!order.isSuccess) {
          final newOrder = order.success();
          upsertOrders.add(newOrder);
        }
      } else if (productDetails != null) {
        final product =
            await _createProduct(productId.createIntent(scene: "restore"), productDetails);
        final newOrder = product.createOrder().success();
        upsertOrders.add(newOrder);
      }
    }
    if (upsertOrders.isNotEmpty) {
      final List<OrderEntity> updatedOrder = [];
      try {
        await GuruDB.instance.upsertOrders(upsertOrders);
        updatedOrder.addAll(upsertOrders);
      } catch (error, stacktrace) {
        Log.w("upsertOrders error:$error $stacktrace", syncCrashlytics: true, syncFirebase: true);
        for (var order in upsertOrders) {
          try {
            await GuruDB.instance.upsertOrder(order: order);
            updatedOrder.add(order);
          } catch (error1, stacktrace1) {
            Log.w("upsertOrder(${order.sku}) error:$error1 $stacktrace1", syncFirebase: true);
          }
        }
      }
      final assets = updatedOrder.map((order) => Asset(order.productId, order)).toList();
      newPurchased.addAllAssets(assets);
    }
    _iapStoreSubject.addEx(newPurchased);
    Log.d("[RestoredPurchases] update purchasedStore ${upsertOrders.length}");
  }

  Future reportFailedOrders() async {
    final failedIapOrders = await AppProperty.getInstance().loadAllFailedIapOrders();
    failedIapOrders.forEach((key, value) async {
      try {
        final order = OrdersReport.fromJson(json.decode(value));
        final result = await GuruApi.instance.reportOrders(order);
        // 这里不管返回什么值，都认为是成功的, 只有崩溃才会返回失败
        await logRevenue(order, result);
        AppProperty.getInstance().removeReportSuccessOrder(key);
      } catch (error, stacktrace) {}
    });
    Log.i("reportFailedOrders success!");
  }

  String buildGooglePlayDetailsString(
      GooglePlayProductDetails googlePlayProduct, GooglePlayPurchaseDetails googlePlayDetails) {
    final StringBuffer sb = StringBuffer();
    sb.writeln("#### purchase ####");

    sb.writeln("productID: ${googlePlayDetails.productID}");
    sb.writeln("purchaseID: ${googlePlayDetails.purchaseID}");
    sb.writeln("transactionDate: ${googlePlayDetails.transactionDate}");

    sb.writeln("status: ${googlePlayDetails.status}");
    sb.writeln("verificationData:");
    sb.writeln(
        "  => localVerificationData: ${googlePlayDetails.verificationData.localVerificationData}");
    sb.writeln(
        "  => serverVerificationData: ${googlePlayDetails.verificationData.localVerificationData}");
    sb.writeln("  => source: ${googlePlayDetails.verificationData.source}");
    sb.writeln("\n#### product ####");
    sb.writeln("price: ${googlePlayProduct.price}");
    sb.writeln("rawPrice: ${googlePlayProduct.rawPrice}");
    sb.writeln("currencyCode: ${googlePlayProduct.currencyCode}");
    sb.writeln("currencySymbol: ${googlePlayProduct.currencySymbol}");
    sb.writeln("productDetails:");

    final productDetails = googlePlayProduct.productDetails;
    sb.writeln("  => description: ${productDetails.name}");
    sb.writeln("  => freeTrialPeriod: ${productDetails.title}");
    sb.writeln("  => description: ${productDetails.description}");
    sb.writeln("  => freeTrialPeriod: ${productDetails.productType}");

    final oneTimeDetails = productDetails.oneTimePurchaseOfferDetails;
    if (oneTimeDetails != null) {
      sb.writeln("  => oneTimeDetails:");
      sb.writeln("    - formattedPrice: ${oneTimeDetails.formattedPrice}");
      sb.writeln("    - priceAmountMicros: ${oneTimeDetails.priceAmountMicros}");
      sb.writeln("    - priceCurrencyCode: ${oneTimeDetails.priceCurrencyCode}");
    }

    final subscriptionOfferDetails = productDetails.subscriptionOfferDetails;
    if (subscriptionOfferDetails != null && subscriptionOfferDetails.isNotEmpty) {
      for (var offer in subscriptionOfferDetails) {
        sb.writeln("  => sub offer: ${offer.offerId}");
        sb.writeln("    - basePlanId: ${offer.basePlanId}");
        sb.writeln("    - offerTag: ${offer.offerTags}");
        sb.writeln("    - offerIdToken: ${offer.offerIdToken}");
        final pricingPhases = offer.pricingPhases;
        for (var idx = 0; idx < pricingPhases.length; ++idx) {
          final phase = pricingPhases[idx];
          sb.writeln("    - pricingPhase[$idx]:");
          sb.writeln("        * billingCycleCount: ${phase.billingCycleCount}");
          sb.writeln("        * billingPeriod: ${phase.billingPeriod}");
          sb.writeln("        * formattedPrice: ${phase.formattedPrice}");
          sb.writeln("        * priceAmountMicros: ${phase.priceAmountMicros}");
          sb.writeln("        * priceCurrencyCode: ${phase.priceCurrencyCode}");
          sb.writeln("        * recurrenceMode: ${phase.recurrenceMode}");
        }
      }
    }

    return sb.toString();
  }

  Future reportOrders(ProductId productId, ProductDetails details, PurchaseDetails purchaseDetails,
      OrderEntity? order) async {
    final OrdersReport ordersReport = OrdersReport();

    if (Platform.isAndroid) {
      ordersReport.token = purchaseDetails.verificationData.serverVerificationData;
      ordersReport.packageName = GuruApp.instance.details.packageName;
      final manifest = order?.manifest;
      final basePlanId = manifest?.basePlanId;
      final offerId = manifest?.offerId;
      if (productId.isSubscription && basePlanId != null && offerId != null) {
        ordersReport.basePlanId = basePlanId;
        ordersReport.offerId = offerId;
      }
      try {
        GooglePlayPurchaseDetails googlePlayDetails = purchaseDetails as GooglePlayPurchaseDetails;
        GooglePlayProductDetails googlePlayProduct = details as GooglePlayProductDetails;
        Log.d(
            "Android Product/Purchase ${buildGooglePlayDetailsString(googlePlayProduct, googlePlayDetails)}");
      } catch (error, stacktrace) {}
    } else if (Platform.isIOS) {
      AppStorePurchaseDetails appleDetails = purchaseDetails as AppStorePurchaseDetails;
      AppStoreProductDetails appleProduct = details as AppStoreProductDetails;
      final StringBuffer sb = StringBuffer();
      sb.writeln("#### purchase ####");
      sb.writeln("productID: ${appleDetails.productID}");
      sb.writeln("purchaseID: ${appleDetails.purchaseID}");
      sb.writeln("transactionDate: ${appleDetails.transactionDate}");
      sb.writeln("verificationData: ${appleDetails.verificationData}");
      sb.writeln("status: ${appleDetails.status}");
      sb.writeln("skPaymentTransaction:");
      sb.writeln(
          "   =>originalTransaction:${appleDetails.skPaymentTransaction.originalTransaction}");
      sb.writeln("       =>${appleDetails.skPaymentTransaction.originalTransaction?.payment}");
      sb.writeln("   =>${appleDetails.skPaymentTransaction.transactionState}:");
      sb.writeln("   =>${appleDetails.skPaymentTransaction.transactionIdentifier}:");
      sb.writeln("\n#### product ####");
      sb.writeln("currencyCode: ${appleProduct.currencyCode}");
      sb.writeln("rawPrice: ${appleProduct.rawPrice}");
      sb.writeln("currencyCode: ${appleProduct.currencyCode}");
      sb.writeln("currencyCode skProduct");
      sb.writeln("    =>localizedTitle: ${appleProduct.skProduct.localizedTitle}");
      sb.writeln("    =>localizedDescription: ${appleProduct.skProduct.localizedDescription}");
      sb.writeln("    =>priceLocale: ${appleProduct.skProduct.priceLocale}");
      sb.writeln("    =>productIdentifier: ${appleProduct.skProduct.productIdentifier}");
      sb.writeln(
          "    =>subscriptionGroupIdentifier: ${appleProduct.skProduct.subscriptionGroupIdentifier}");
      sb.writeln("    =>appleProduct.skProduct.priceLocale");
      sb.writeln("        ->{appleProduct.skProduct.priceLocale}");

      ordersReport.bundleId = GuruApp.instance.appSpec.details.bundleId;
      ordersReport.receipt = purchaseDetails.verificationData.serverVerificationData;
      ordersReport.sku = appleDetails.productID;
      ordersReport.countryCode = appleProduct.skProduct.priceLocale.countryCode;
      Log.d("IOS Product/Purchase ${sb.toString()}");
    }

    if (productId.isSubscription) {
      ordersReport.orderType = OrderType.subs;
      ordersReport.subscriptionId = details.id;
    } else {
      ordersReport.orderType = OrderType.inapp;
      ordersReport.productId = details.id;
    }
    ordersReport.orderId = purchaseDetails.purchaseID;
    ordersReport.price = details.rawPrice.toString();
    ordersReport.currency = details.currencyCode;
    ordersReport.orderUserInfo = OrderUserInfo(GuruSettings.instance.bestLevel.get().toString());
    ordersReport.userIdentification = GuruAnalytics.instance.userIdentification;

    final transactionDate = purchaseDetails.transactionDate;
    if (transactionDate != null && transactionDate.isNotEmpty) {
      try {
        final ts = int.tryParse(transactionDate) ?? DateTimeUtils.currentTimeInMillis();
        ordersReport.transactionDate = ts;
      } catch (error, stacktrace) {
        Log.w("parse transactionDate error! $error", stackTrace: stacktrace);
      }
    }

    Log.d("orderReport:$ordersReport", tag: "Iap");
    try {
      final result = await GuruApi.instance.reportOrders(ordersReport);
      // 这里不管返回什么值，都认为是成功的
      await logRevenue(ordersReport, result);
      return;
    } catch (error, stacktrace) {
      Log.i("reportOrders error!", error: error, stackTrace: stacktrace);
    }
    AppProperty.getInstance().saveFailedIapOrders(ordersReport);
  }

  Future<bool> logRevenue(OrdersReport order, OrdersResponse result) async {
    final isSubscription = order.orderType == OrderType.subs;
    final sku = (isSubscription ? order.subscriptionId : order.productId) ??
        (order.productId ?? order.subscriptionId ?? order.sku);
    final usdPrice = result.usdPrice;
    if (sku == null || sku.isEmpty) {
      return false;
    }

    if (!result.isTestOrder && usdPrice <= 0) {
      Log.i("ignoreInvalidResult $result", tag: "Iap");
      return false;
    }
    Log.i("prepare logRevenue! $result $sku");

    final platform = Platform.isIOS ? "appstore" : "google_play";
    if (GuruApp.instance.appSpec.deployment.purchaseEventTrigger == 2) {
      GuruAnalytics.instance.logAdRevenue020(usdPrice, platform, "USD",
          orderId: order.orderId,
          orderType: isSubscription ? "SUB" : "IAP",
          productId: sku,
          transactionDate: order.transactionDate);
    } else {
      GuruAnalytics.instance.logAdRevenue(usdPrice, platform, "USD",
          orderId: order.orderId,
          orderType: isSubscription ? "SUB" : "IAP",
          productId: sku,
          transactionDate: order.transactionDate);
    }
    GuruAnalytics.instance
        .logPurchase(usdPrice, currency: 'USD', contentId: sku, adPlatform: platform);
    if (isSubscription) {
      GuruAnalytics.instance.logEvent(
          "sub_purchase",
          {
            "platform": platform,
            "currency": "USD",
            "revenue": usdPrice,
            "product_id": sku,
            "order_type": "SUB",
            "order_id": order.orderId,
            "trans_ts": order.transactionDate
          },
          options: iapRevenueAppEventOptions);
    } else {
      GuruAnalytics.instance.logEvent(
          "iap_purchase",
          {
            "platform": platform,
            "currency": "USD",
            "revenue": usdPrice,
            "product_id": sku,
            "order_type": "IAP",
            "order_id": order.orderId,
            "trans_ts": order.transactionDate
          },
          options: iapRevenueAppEventOptions);
    }
    GuruAnalytics.instance.logGuruEvent(
        "dev_iap_action", {"item_category": "reported", "item_name": sku, "result": "true"});
    Log.i("reportOrders completed! logRevenue success! $result $sku");
    return true;
  }

  Future _deliverManifest(ProductId productId, Manifest manifest) async {
    bool result = false;
    String cause = '';
    try {
      result = await ManifestManager.instance
          .deliver(manifest, TransactionMethod.iap)
          .catchError((error) {
        Log.w("applyManifest error:$error", syncCrashlytics: true, syncFirebase: true);
      });
    } catch (error, stacktrace) {
      cause = error.toString();
    }
    GuruAnalytics.instance.logGuruEvent("dev_iap_action", {
      "item_category": "delivered",
      "item_name": productId.sku,
      "mc": manifest.category,
      "result": result ? "true" : "false",
      'cause': cause,
    });
  }

  Future<bool> _completeOrder(OrderEntity order) async {
    bool result = false;
    try {
      final completedOrder = order.success();
      result = await GuruDB.instance.completeOrder(order: completedOrder);
    } catch (error, stacktrace) {
      Log.w("_completePurchase error.$error",
          stackTrace: stacktrace, syncFirebase: true, syncCrashlytics: true);
    }

    GuruAnalytics.instance.logGuruEvent("dev_iap_action", {
      "item_category": "complete",
      "item_name": order.productId.sku,
      "mc": order.manifest?.category ?? "unknown",
      "result": result ? "true" : "false"
    });
    final manifest = order.manifest;
    if (manifest != null) {
      await _deliverManifest(order.productId, manifest);
    }
    if (order.isAsset || order.isSubscription) {
      final changedPurchasedStore = purchasedStore.clone();
      changedPurchasedStore.addAsset(Asset(order.productId, order));
      _iapStoreSubject.addEx(changedPurchasedStore);
    }
    return true;
  }

  Future completePoints(
      ProductId productId, ProductDetails productDetails, PurchaseDetails details) async {
    final count = await AppProperty.getInstance().increaseAndGetIapCount();
    GuruAnalytics.instance.setUserProperty("purchase_count", count.toString());
    try {
      final cost = productDetails.rawPrice;
      if (cost > 0) {
        final double price = cost;
        if (count == 1) {
          GuruAnalytics.instance.logEventEx("first_iap",
              itemName: productId.sku,
              value: price,
              parameters: {"currency": productDetails.currencyCode});
          GuruAnalytics.instance.setUserProperty("is_iap_user", "true");
        }
        GuruAnalytics.instance.logEventEx(productId.iapEventName,
            itemName: productId.sku,
            value: price,
            parameters: {"currency": productDetails.currencyCode});
      }
    } catch (error, stacktrace) {
      GuruAnalytics.instance.logException(error, stacktrace: stacktrace);
    }
    final intent = productId.createIntent(scene: "outside_points");
    final manifest = await ManifestManager.instance.createManifest(intent);
    await _deliverManifest(productId, manifest);
    // 这里不需要传 order，因为 points 商品是非订阅商品
    await reportOrders(productId, productDetails, details, null);
  }

  Future<OrderEntity?> _completePurchase(ProductId definedProductId,
      ProductDetails originProductDetails, PurchaseDetails details) async {
    ProductId productId = definedProductId;
    await _inAppPurchase.completePurchase(details);
    final count = await AppProperty.getInstance().increaseAndGetIapCount();
    GuruAnalytics.instance.setUserProperty("purchase_count", count.toString());
    Log.d("_completePurchase $productId ${details.pendingCompletePurchase}", tag: "Iap");
    OrderEntity? resultOrder;

    IapRequest? iapRequest = iapRequestMap.remove(productId);
    if (iapRequest == null) {
      final offerProductIds = GuruApp.instance.offerProductIds(productId);
      for (var offerProductId in offerProductIds) {
        iapRequest = iapRequestMap.remove(offerProductId);
        if (iapRequest != null) {
          productId = offerProductId;
          break;
        }
      }
    }
    if (iapRequest != null) {
      resultOrder = iapRequest.order;
      final result = await _completeOrder(iapRequest.order);
      iapRequest.response(result);
    } else {
      Log.d("Not found iapRequest for $productId");
      final orders = await GuruDB.instance.getPendingOrders(productId);
      if (orders.isNotEmpty) {
        orders.sort((a, b) => b.timestamp.compareTo(a.timestamp));
        await _completeOrder(orders.first);
        resultOrder = orders.first;
      }
    }

    final productDetails = iapRequest?.product.offerDetails ??
        iapRequest?.product.details ??
        loadedProductDetails[productId] ??
        originProductDetails;

    Log.d(
        "productId:$productId productDetails:${productDetails.rawPrice} originProductDetails:${originProductDetails.rawPrice}");

    try {
      // final item = pendingTransaction.product.item;
      final cost = productDetails.rawPrice;
      if (cost > 0) {
        final double price = cost;
        if (count == 1) {
          GuruAnalytics.instance.logEventEx("first_iap",
              itemName: productId.sku,
              value: price,
              parameters: {"currency": productDetails.currencyCode});
          GuruAnalytics.instance.setUserProperty("is_iap_user", "true");
        }
        GuruAnalytics.instance.logEventEx(productId.iapEventName,
            itemName: productId.sku,
            value: price,
            parameters: {"currency": productDetails.currencyCode});
      }
    } catch (error, stacktrace) {
      GuruAnalytics.instance.logException(error, stacktrace: stacktrace);
    }
    if (productId.isSubscription) {
      if (resultOrder != null) {
        recordSubscription(resultOrder);
      }
    }
    if (resultOrder != null) {
      reportOrders(productId, productDetails, details, resultOrder);
    }

    return resultOrder;
  }

  Future recordSubscription(OrderEntity order) async {
    final sku = order.sku;
    final manifest = order.manifest;
    final productId = ProductId.fromSku(
        sku: sku,
        attr: TransactionAttributes.subscriptions,
        basePlan: manifest?.basePlanId,
        offerId: manifest?.offerId);

    final group = GuruApp.instance.appSpec.productProfile.group(productId);

    final appProperty = AppProperty.getInstance();

    await appProperty.getAndIncrease(PropertyKeys.subscriptionCount);
    if (group != null) {
      await appProperty.getAndIncrease(PropertyKeys.buildGroupSubscriptionCount(group));
    }
    await appProperty.getAndIncrease(PropertyKeys.buildSubscriptionCount(productId));
  }

  Future<Manifest> createPurchaseManifest(TransactionIntent intent) {
    return ManifestManager.instance.createManifest(intent);
  }

  Future<ProductDetails?> checkAndDistributeOfferDetails(
      ProductId productId, ProductDetails? details, EligibilityCriteria eligibilityCriteria) async {
    Log.d("checkAndDistributeOfferDetails $productId $eligibilityCriteria");
    switch (eligibilityCriteria) {
      case EligibilityCriteria.newCustomerNeverHadSubscribedThisGroup:
        final group = GuruApp.instance.appSpec.productProfile.group(productId);
        if (group != null) {
          final key = PropertyKeys.buildGroupSubscriptionCount(group);
          final count = await AppProperty.getInstance().getInt(key, defValue: 0);
          Log.d("  ==> $key $count");
          return count > 0 ? null : details;
        }
        Log.d("  ==> not found group($group)! return null");
        break;
      case EligibilityCriteria.newCustomerNeverHadThisSubscription:
        final key = PropertyKeys.buildSubscriptionCount(productId);
        final count = await AppProperty.getInstance().getInt(key, defValue: 0);
        Log.d("  ==> $key $count");
        return count > 0 ? null : details;
      case EligibilityCriteria.newCustomerNeverHadAnySubscription:
        final count =
            await AppProperty.getInstance().getInt(PropertyKeys.subscriptionCount, defValue: 0);
        Log.d("  ==> subscriptionCount $count");
        return count > 0 ? null : details;
      default:
        return details;
    }
    return null;
  }

  Future<IapProduct> _createProduct(TransactionIntent intent, ProductDetails details) async {
    final productId = intent.productId;
    Manifest manifest = await ManifestManager.instance.createManifest(intent);
    Log.d("createProduct ${productId.sku} ${productId.hasBasePlan}");
    ProductDetails baseDetails = details;
    ProductDetails? offerDetails;
    if (Platform.isAndroid && productId.isSubscription && productId.hasBasePlan) {
      final googlePlayProductDetails = details as GooglePlayProductDetails;
      final productDetails = googlePlayProductDetails.productDetails;
      final subscriptionOfferDetails = productDetails.subscriptionOfferDetails;
      final offerProductDetails = GooglePlayProductDetails.fromProductDetails(productDetails);
      final expectBasePlan = productId.basePlan;
      final expectOfferId = productId.offerId;
      Log.d(
          "expectOfferId:$expectOfferId expectBasePlan:$expectBasePlan offers:${offerProductDetails.length}");

      if (expectBasePlan != null &&
          subscriptionOfferDetails != null &&
          subscriptionOfferDetails.length >= offerProductDetails.length) {
        for (int i = 0; i < subscriptionOfferDetails.length; i++) {
          final offer = subscriptionOfferDetails[i];
          Log.d(
              "$i expectOfferId:$expectOfferId offerId:${offer.offerId} expectBasePlan:$expectBasePlan basePlanId:${offer.basePlanId}");
          if (expectBasePlan != offer.basePlanId) {
            continue;
          }
          if (offer.offerId == null) {
            baseDetails = offerProductDetails[i];
          } else if (expectOfferId != null && expectOfferId == offer.offerId) {
            offerDetails = offerProductDetails[i];
          }
        }
        try {
          offerDetails = await checkAndDistributeOfferDetails(
              productId, offerDetails, intent.eligibilityCriteria);
        } catch (error, stacktrace) {
          Log.w("checkAndDistributeOfferDetails error! $error $stacktrace");
        }
      }
    }
    return Product.iap(productId, baseDetails, manifest, offerDetails: offerDetails) as IapProduct;
  }

  Future<ProductStore<IapProduct>> buildProducts(Set<TransactionIntent> intents) async {
    ProductStore<IapProduct> iapStore = ProductStore();
    final _productDetails = loadedProductDetails;
    for (var intent in intents) {
      // 这里需要使用原始 ID 进行查找
      final productId = intent.productId.originId;
      final details = _productDetails[productId];
      Log.d("buildProducts $productId $details");
      if (details == null) {
        continue;
      }
      final product = await _createProduct(intent, details);
      iapStore.putProduct(product);
      if (intent.productId.hasOffer && !iapStore.existsProduct(productId)) {
        final originProduct =
            await _createProduct(productId.createIntent(scene: intent.scene), details);
        iapStore.putProduct(originProduct);
      }
    }

    return iapStore;
  }

  Future<bool> buy(IapProduct product) async {
    final productId = product.productId.originId;
    final asset = purchasedStore.getAsset(productId);
    if (asset != null) {
      Log.v("IAP buy ${asset.productId} direct success!");
      return true;
    }
    final pendingProduct = iapRequestMap[productId];
    if (pendingProduct != null) {
      Log.v("_requestPurchases has pending product");
      return pendingProduct.completer.future;
    }
    final param = PurchaseParam(
        productDetails: product.offerDetails ?? product.details,
        applicationUserName: AccountDataStore.instance.user?.uid);

    late OrderEntity order;
    try {
      order = product.createOrder();
      await GuruDB.instance.upsertOrder(order: order);
    } catch (error, stacktrace) {
      Log.w("addOrder error! $error",
          stackTrace: stacktrace, syncCrashlytics: true, syncFirebase: true);
      return false;
    }

    bool result = false;
    if (product.isConsumable()) {
      result = await _inAppPurchase.buyConsumable(purchaseParam: param);
    } else {
      result = await _inAppPurchase.buyNonConsumable(purchaseParam: param);
    }
    if (!result) {
      Log.d("_requestPurchases error! ${product.productId} ${product.details.price}",
          syncFirebase: true);
      GuruAnalytics.instance.logGuruEvent("dev_iap_action", {
        "item_category": "request",
        "item_name": order.productId.sku,
        "mc": order.manifest?.category ?? "unknown",
        "result": "false",
        "cause": "buy error"
      });
      await GuruDB.instance.deleteOrder(order: order);
      return false;
    } else {
      GuruAnalytics.instance.logGuruEvent("dev_iap_action", {
        "item_category": "request",
        "item_name": order.productId.sku,
        "mc": order.manifest?.category ?? "unknown",
        "result": "true"
      });
    }
    final completer = Completer<bool>();
    final iapRequest = IapRequest(product, order, completer);
    iapRequestMap[productId] = iapRequest;
    return await completer.future;
  }

  Future clearAssetRecord() async {
    if (!Platform.isAndroid) {
      return;
    }
    final InAppPurchaseAndroidPlatformAddition androidAddition =
        _inAppPurchase.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
    final response = await androidAddition.queryPastPurchases();
    for (var purchase in response.pastPurchases) {
      androidAddition.consumePurchase(purchase);
      Log.w("[clearAssetRecord] purchase clear:${purchase.productID} ${purchase.verificationData}");
      _inAppPurchase.completePurchase(purchase);
    }

    await GuruDB.instance.clearOrders(method: TransactionMethod.iap);
    final newPurchased = AssetsStore<IapAsset>();
    _iapStoreSubject.addEx(newPurchased);
  }

  Future manualConsumePurchase(PurchaseDetails purchase) async {
    if (Platform.isAndroid) {
      final InAppPurchaseAndroidPlatformAddition androidAddition =
          _inAppPurchase.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
      await androidAddition.consumePurchase(purchase);
      _inAppPurchase.completePurchase(purchase);
      await GuruDB.instance.deletePendingOrderBySku(sku: purchase.productID);
    }
  }

  Future manualConsumeAllPurchases(List<Tuple2<ProductId, PurchaseDetails>> tuples) async {
    for (var tuple in tuples) {
      try {
        final productId = tuple.item1;
        final purchase = tuple.item2;
        await manualConsumePurchase(purchase);
      } catch (error, stacktrace) {
        Log.w("consumePurchase error! $error", stackTrace: stacktrace, syncFirebase: true);
      }
    }
  }

  Future completeAllPurchases(List<Tuple2<ProductId, PurchaseDetails>> tuples) async {
    for (var tuple in tuples) {
      try {
        final productId = tuple.item1;
        final details = tuple.item2;
        final productDetails = loadedProductDetails[productId];
        if (productDetails != null) {
          if (details.pendingCompletePurchase) {
            GuruAnalytics.instance.logGuruEvent("dev_iap_action", {
              "item_category": "pending_complete",
              "item_name": productId.sku,
              "result": "true",
            });
            final order = await _completePurchase(productId, productDetails, details);
          } else {
            GuruAnalytics.instance.logGuruEvent("dev_iap_action", {
              "item_category": "pending_consume",
              "item_name": productId.sku,
              "result": "true",
            });
            await manualConsumePurchase(details);
            if (productId.isPoints) {
              try {
                await completePoints(productId, productDetails, details);
              } catch (error, stacktrace) {
                Log.w("completePoints error! $error", stackTrace: stacktrace, syncFirebase: true);
              }
            }
          }
        }
      } catch (error, stacktrace) {
        Log.w("consumePurchase error! $error", stackTrace: stacktrace, syncFirebase: true);
      }
    }
  }

//
// Future<List<IapProduct>> refreshAllProducts() async {
//   final allProductIds = ProductIds.allIapProductIds;
//
//   return await refreshProducts(allProductIds);
// }

  Map<String, ProductId> _filterProductSkus(
      {required Set<ProductId> ids, required Set<int> attrs, Set<ProductId>? validIds}) {
    final List<MapEntry<String, ProductId>> entries = ids
        .where((productId) =>
            (validIds?.contains(productId) != false) && attrs.contains(productId.attr))
        .map((productId) => MapEntry(productId.sku, productId))
        .toList();
    return Map.fromEntries(entries);
  }

  Future<ProductDetailsResponse> _queryProducts(Set<String> skus) async {
    try {
      return await _inAppPurchase.queryProductDetails(skus);
    } catch (error, stacktrace) {
      Log.i("_getProducts error:$error $stacktrace");
    }
    return _emptyResponse;
  }

  Future restorePurchases() async {
    Log.d("restorePurchases!");
    if (!iapAvailable) {
      Log.w("ignore restorePurchases! iap service not available!", tag: "IAP");
      return;
    }
    if (!_restorePurchase) {
      _restorePurchase = Platform.isAndroid; // 只有Android需要进行处理
      return await _inAppPurchase.restorePurchases();
    }
  }

  Future refreshProducts() async {
    if (!iapAvailable) {
      Log.w("ignore refreshProducts! iap service not available!", tag: "IAP");
      return;
    }
    final validIds = GuruApp.instance.productProfile.oneOffChargeIapIds.toSet()
      ..removeAll(loadedProductDetails.keys.toSet());
    final queryOneOffChargeSkuMap = _filterProductSkus(
        ids: GuruApp.instance.productProfile.oneOffChargeIapIds,
        attrs: TransactionAttributes.oneOffChargeAttributes,
        validIds: validIds);

    Log.i("refreshProduct $queryOneOffChargeSkuMap", tag: "IAP");
    final Map<ProductId, ProductDetails> detailsMap = {};

    if (queryOneOffChargeSkuMap.isEmpty) {
      Log.i("refreshProducts ignore! already loaded!", tag: "IAP");
      return;
    }

    final queryProductIds = queryOneOffChargeSkuMap.keys.toSet();
    queryProductIds.addAll(GuruApp.instance.productProfile.subscriptionsIapIds.map((e) => e.sku));
    Log.d("refresh product:", tag: "IAP");
    for (var productId in queryProductIds) {
      Log.d("  => $productId", tag: "IAP");
    }
    final response = await _queryProducts(queryProductIds).catchError((error, stacktrace) {
      Log.e("getProducts($queryOneOffChargeSkuMap}) error: $error $stacktrace", tag: "IAP");
      return _emptyResponse;
    });
    Log.i("refreshProduct COMPLETED:", tag: "IAP");
    for (var details in response.productDetails) {
      Log.i(" => ${details.id}", tag: "IAP");
    }

    Log.i("refreshProduct notFoundId:", tag: "IAP");
    for (var id in response.notFoundIDs) {
      Log.i(" => $id", tag: "IAP");
    }

    for (var details in response.productDetails) {
      detailsMap.addAll(extractProducts(details));
    }

    GuruAnalytics.instance
        .logGuruEvent("dev_iap_action", {"item_category": "load", "result": "true"});
    final newProductDetails = Map.of(loadedProductDetails);
    newProductDetails.addAll(detailsMap);
    _productDetailsSubject.addEx(newProductDetails);
  }

  Map<ProductId, ProductDetails> extractProducts(ProductDetails details) {
    final productId = GuruApp.instance.findProductId(sku: details.id);
    final Map<ProductId, ProductDetails> detailsMap = {};
    if (productId == null) {
      return detailsMap;
    }

    detailsMap[productId] = details;

    final ids = GuruApp.instance.offerProductIds(productId);
    if (ids.isNotEmpty) {
      final googlePlayProductDetails = details as GooglePlayProductDetails;
      final productDetails = googlePlayProductDetails.productDetails;
      final subscriptionOfferDetails = productDetails.subscriptionOfferDetails;
      final offerProductDetails = GooglePlayProductDetails.fromProductDetails(productDetails);
      for (var id in ids) {
        final expectBasePlan = id.basePlan;
        final expectOfferId = id.offerId;
        if (expectBasePlan != null &&
            subscriptionOfferDetails != null &&
            subscriptionOfferDetails.length == offerProductDetails.length) {
          for (int i = 0; i < subscriptionOfferDetails.length; i++) {
            final offer = subscriptionOfferDetails[i];
            Log.d(
                "$i expectOfferId:$expectOfferId offerId:${offer.offerId} expectBasePlan:$expectBasePlan basePlanId:${offer.basePlanId}");
            if (expectBasePlan != offer.basePlanId || expectOfferId != offer.offerId) {
              continue;
            }
            detailsMap[id] = offerProductDetails[i];
          }
        }
      }
    }
    return detailsMap;
  }
}
