import 'package:guru_app/analytics/guru_analytics.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/igc/igc_model.dart';
import 'package:guru_app/financial/manifest/manifest_manager.dart';
import 'package:guru_app/financial/product/product_model.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/property/app_property.dart';

/// Created by Haoyi on 2023/2/18

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

  static IgcManager get instance => _instance;

  final BehaviorSubject<int> _balanceSubject = BehaviorSubject.seeded(0);

  final BehaviorSubject<AssetsStore<Asset>> _assetStoreSubject =
      BehaviorSubject.seeded(AssetsStore<Asset>.inactive());

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

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

  int get currentBalance => _balanceSubject.value;

  Stream<int> get observableCurrentBalance => _balanceSubject.stream;

  final CompositeSubscription subscriptions = CompositeSubscription();

  IgcManager._();

  Future init() async {
    final attachedGems = (await AppProperty.getInstance().isFirstUseGemsFeature())
        ? GuruApp.instance.appSpec.deployment.initIgc
        : 0;
    final _balance = await AppProperty.getInstance().accumulateIgc(attachedGems);
    _balanceSubject.addEx(_balance);
    GuruAnalytics.instance.setUserProperty("coin", _balance.toString());

    final iapIgc = await AppProperty.getInstance().getIapIgc();
    GuruAnalytics.instance.setUserProperty("iap_coin", iapIgc.toString());

    final noIapIgcGems = await AppProperty.getInstance().getNoIapIgc();
    GuruAnalytics.instance.setUserProperty("noniap_coin", noIapIgcGems.toString());
    await reloadAssets();
  }

  Future switchSession() async {
    init();
  }

  Future reloadAssets() async {
    final orders = await GuruDB.instance
        .selectOrders(method: TransactionMethod.igc, attrs: [TransactionAttributes.asset]);
    final newAssetStore = AssetsStore<Asset>();
    for (var order in orders) {
      final productId = order.productId;
      Log.v("init order:${order.sku} $productId");
      newAssetStore.addAsset(Asset(productId, order));
    }
    _assetStoreSubject.addEx(newAssetStore);
  }

  Future clearAssets({String? category, TransactionMethod? method}) async {
    await GuruDB.instance.clearOrders(category: category, method: method);
    final newAssetStore = purchasedStore.clone()..clearAsset(category: category, method: method);
    _assetStoreSubject.addEx(newAssetStore);
  }

  Future<bool> accumulate(int igc, TransactionMethod method, {String? scene}) async {
    try {
      int newBalance = await AppProperty.getInstance().accumulateIgc(igc);
      try {
        if (method == TransactionMethod.iap) {
          final iapIgc = await AppProperty.getInstance().accumulateIapIgc(igc);
          await GuruAnalytics.instance.setUserProperty("iap_coin", iapIgc.toString());
        } else {
          final noniapIgc = await AppProperty.getInstance().accumulateNoIapIgc(igc);
          await GuruAnalytics.instance.setUserProperty("noniap_coin", noniapIgc.toString());
        }
        await GuruAnalytics.instance.setUserProperty("coin", newBalance.toString());
      } catch (throwable, stacktrace) {
        Log.w("accumulate error $throwable", syncFirebase: true, syncCrashlytics: true);
      }
      _balanceSubject.add(newBalance);

      GuruAnalytics.instance.logEarnVirtualCurrency(
          virtualCurrencyName: "coin",
          method: scene ?? convertTransactionMethodName(method),
          balance: newBalance,
          value: igc);
      return true;
    } catch (error, stacktrace) {
      Log.v("accumulate error:$error $stacktrace");
    }
    return false;
  }

  Future clear() async {
    final result = await AppProperty.getInstance().clearAllIgc();
    if (result) {
      _balanceSubject.add(0);
    }
  }

  Future<IgcProduct> buildIgcProduct(TransactionIntent intent) async {
    final manifest = await ManifestManager.instance.createManifest(intent);
    return IgcProduct(intent.productId, manifest, intent.igcCost);
  }

  Future<bool> purchase(IgcProduct product) async {
    Log.v("Igc buy");

    final purchasedItem = purchasedStore.getAsset(product.productId);
    if (purchasedItem != null) {
      Log.v("Coin buy ${purchasedItem.productId} direct success!");
      return true;
    }
    return _requestPurchase(product);
  }

  Future<bool> _requestPurchase(IgcProduct product) async {
    if (currentBalance < product.cost || product.cost < 0) {
      Log.v("_requestPurchase error! $currentBalance price:${product.cost}");
      return false;
    }
    try {
      final int newBalance = await AppProperty.getInstance().consumeIgc(product.cost);
      await GuruAnalytics.instance.setUserProperty("coin", newBalance.toString());

      if (currentBalance != newBalance) {
        _balanceSubject.add(newBalance);
      }

      if (product.cost != 0) {
        GuruAnalytics.instance.logSpendCredits(
            product.productId.sku, product.manifest.category, product.cost,
            virtualCurrencyName: "coin", balance: newBalance, scene: product.manifest.scene);
      }

      if (!product.productId.isConsumable) {
        final order = product.createOrder();
        await GuruDB.instance.upsertOrder(order: order).catchError((error) {
          Log.v("upsertOrder error!$error");
          return false;
        });
        final newPurchasedStore = purchasedStore.clone();
        newPurchasedStore.addAsset(Asset(product.productId, order));
        _assetStoreSubject.addEx(newPurchasedStore);
      }
      return true;
    } catch (error, stacktrace) {
      Log.v("error $error, $stacktrace");
      return false;
    }
  }

  void dispose() {
    // _productStoreSubject.close();
    _assetStoreSubject.close();
    _balanceSubject.close();
  }
}
