import 'dart:async';

import 'package:guru_app/financial/igc/igc_manager.dart';
import 'package:guru_app/financial/product/product_model.dart';
import 'package:guru_app/inventory/inventory_manager.dart';
import 'manifest.dart';

/// Created by Haoyi on 2022/8/21

// typedef DetailsDistributor = Future<bool> Function(Details, TransactionMethod, String scene);

typedef DetailsDistributor = Future<List<StockItem>> Function(
    Details, TransactionMethod, String scene);

typedef ManifestBuilder = Future<Manifest?> Function(TransactionIntent);

class ManifestManager {
  ManifestManager._() {
    observableDeliveredManifest = deliveredManifestStream.stream.asBroadcastStream();
  }

  final StreamController<Manifest> deliveredManifestStream = StreamController();

  static final ManifestManager instance = ManifestManager._();

  late Stream<Manifest> observableDeliveredManifest;

  final Map<String, DetailsDistributor> distributors = {
    DetailsReservedType.igc: _deliverIgcDetails
  };

  final List<ManifestBuilder> builders = [];

  static Future<List<StockItem>> _deliverIgcDetails(
      Details details, TransactionMethod method, String scene) async {
    if (details.amount > 0) {
      await IgcManager.instance.accumulate(details.amount, method, scene: scene);
    }
    return [];
  }

  static Future<List<StockItem>> _deliverDefaultDetails(
      Details details, TransactionMethod method, String scene) async {
    if (details.amount > 0) {
      return [StockItem.fromDetails(details)];
    }
    return [];
  }

  // static Future<bool> _deliverDetails(
  //     Details details, TransactionMethod method, String scene) async {
  //   final stock = StockItem.fromDetails(details);
  //   if (stock.amount > 0) {
  //     await InventoryManager.instance.acquire([stock], method, scene);
  //   }
  //   return false;
  // }

  void addDistributor(String type, DetailsDistributor distributor) {
    distributors[type] = distributor;
  }

  void addBuilder(ManifestBuilder builder) {
    builders.add(builder);
  }

  void addBuilders(List<ManifestBuilder> builders) {
    this.builders.addAll(builders);
  }

  Future _acquire(List<StockItem> items, TransactionMethod method, Manifest manifest) async {
    if (items.isNotEmpty) {
      String specific = "";
      switch (method) {
        case TransactionMethod.iap:
          specific = manifest.contentId;
          break;
        case TransactionMethod.igc:
          specific = "coin";
          break;
        case TransactionMethod.igb:
          specific = manifest.barterId;
          break;
        default:
          specific = manifest.scene;
          break;
      }
      await InventoryManager.instance.acquire(items, method, specific, scene: manifest.scene);
    }
  }

  Future<List<StockItem>> deliverStockItems(
      List<StockItem> items, Manifest manifest, TransactionMethod method) async {
    final List<StockItem> unsold = [];
    for (var item in items) {
      if (item.sku == DetailsReservedType.igc) {
        await distributors[DetailsReservedType.igc]
            ?.call(Details.define(DetailsReservedType.igc, item.amount), method, manifest.scene);
        continue;
      }
      unsold.add(item);
    }
    return unsold;
  }

  Future<bool> deliver(Manifest manifest, TransactionMethod method) async {
    bool result = false;
    final List<StockItem> unsold = [];
    for (var details in manifest.details) {
      final items = await distributors[details.type]?.call(details, method, manifest.scene) ??
          await _deliverDefaultDetails(details, method, manifest.scene);
      final unsoldItems = await deliverStockItems(items, manifest, method);
      unsold.addAll(unsoldItems);
      result |= unsoldItems.isEmpty;
    }
    if (unsold.isNotEmpty) {
      await _acquire(unsold, method, manifest);
    }

    deliveredManifestStream.add(manifest);
    return result;
  }

  Future<Manifest> createManifest(TransactionIntent intent) async {
    for (var builder in builders) {
      final manifest = await builder(intent);
      if (manifest != null) {
        return manifest;
      }
    }
    return Manifest.empty;
  }

  Manifest createIgcManifest(int igc, {String? category, String scene = ""}) {
    final details = <Details>[];
    details.add(Details.define(DetailsReservedType.igc, igc));

    final extras = <String, dynamic>{ExtraReservedField.scene: scene};
    return Manifest(category ?? DetailsReservedType.igc, extras: extras, details: details);
  }

// Manifest createIgbManifest(int igc, {String? category, String scene = ""}) {
//   final details = <Details>[];
//   details.add(Details.define(DetailsReservedType.igc, igc));
//
//   final extras = <String, dynamic>{ExtraReservedField.scene: scene};
//   return Manifest(category ?? DetailsReservedType.igc, extras: extras, details: details);
// }
}
