import 'dart:convert';
import 'dart:io';

import 'package:json_annotation/json_annotation.dart';

/// Created by Haoyi on 2021/7/1

part "manifest.g.dart";

class DetailsReservedType {
  static const String igc = "igc";
  static const String igb = "igb";
}

class DetailsAttr {
  static const permanent = 1;
  static const consumable = 2;
}

class ExtraReservedField {
  static const String scene = "__scene";
  static const String offerId = "__offer_id";
  static const String basePlanId = "__base_plan_id";
  static const String sales = "__sales";
  static const String rate = "__rate";
  static const String contentId = "__contentId";
  static const String barterId = "__barterId";
  static const String transactionTs = "__transaction_ts";
}

class DetailsReservedField {
  static const String type = "__type";
  static const String sku = "__sku";
  static const String amount = "__amount";
  static const String name = "__name";
  static const String icon = "__icon";
  static const String attr = "__attr";
}

class ManifestStringConvert implements JsonConverter<Manifest, String> {
  const ManifestStringConvert();

  @override
  Manifest fromJson(String json) {
    if (json.isEmpty) {
      return Manifest.empty;
    }
    final map = jsonDecode(json);
    return Manifest.fromJson(map);
  }

  @override
  String toJson(Manifest manifest) {
    return jsonEncode(manifest);
  }
}

const ManifestStringConvert manifestStringConvert = ManifestStringConvert();

@JsonSerializable(constructor: "_")
class Details {
  @JsonKey(name: "bundle", defaultValue: <String, dynamic>{})
  final Map<String, dynamic> bundle;

  const Details._({this.bundle = const <String, dynamic>{}});

  Details.define(String type, int amount,
      {String? sku, Map<String, dynamic> params = const <String, dynamic>{}})
      : bundle = <String, dynamic>{
          DetailsReservedField.sku: sku ?? type,
          DetailsReservedField.type: type,
          DetailsReservedField.amount: amount
        };

  factory Details.fromJson(Map<String, dynamic> json) => _$DetailsFromJson(json);

  int get amount => bundle[DetailsReservedField.amount] ?? 0;

  String get type => bundle[DetailsReservedField.type] ?? "unknown";

  String get sku => bundle[DetailsReservedField.sku] ?? type;

  String? get name => bundle[DetailsReservedField.name] ?? "unknown";

  int get attr => bundle[DetailsReservedField.attr] ?? DetailsAttr.consumable;

  Map<String, dynamic> toJson() => _$DetailsToJson(this);

  void merge(Details details) {
    bundle.addAll(details.bundle);
  }

  bool containsKey(String name) {
    return bundle.containsKey(name);
  }

  void setInt(String key, int value) {
    bundle[key] = value.toString();
  }

  void setDouble(String key, double value) {
    bundle[key] = value.toString();
  }

  void setString(String key, String value) {
    bundle[key] = value;
  }

  void setBool(String key, bool value) {
    bundle[key] = value;
  }

  int? getInt(String key) {
    final value = bundle[key];
    if (value == null) {
      return null;
    }
    return int.parse(value);
  }

  double? getDouble(String key) {
    final value = bundle[key];
    if (value == null) {
      return null;
    }
    return double.parse(value);
  }

  String? getString(String key) {
    return bundle[key];
  }

  bool? getBool(String key) {
    final value = bundle[key];
    if (value == null) {
      return null;
    }
    return value == true;
  }

  Future<void> forEach(void Function(String key, String value) f) async {
    for (var entry in bundle.entries) {
      f.call(entry.key, entry.value);
    }
  }
}

@JsonSerializable(explicitToJson: true)
class Manifest {
  @JsonKey(name: "category", defaultValue: "")
  final String category;

  @JsonKey(name: "extras", defaultValue: <String, dynamic>{})
  final Map<String, dynamic> extras;

  @JsonKey(name: "details", defaultValue: <Details>[])
  final List<Details> details;

  String get scene => extras[ExtraReservedField.scene] ?? "";

  String? get basePlanId => extras[ExtraReservedField.basePlanId];

  String? get offerId => extras[ExtraReservedField.offerId];

  /// 在 TransactionMethod为IAP时，specific 对应的是 IAP 的 SKU，
  /// 如一些特定含有 ID的物品，如用 IGC购买了某个道具，这里面的 contentId 就是道具的 ID
  String get contentId => extras[ExtraReservedField.contentId] ?? "";

  // 如果你Barter进行购买，这个字段会有值
  String get barterId => extras[ExtraReservedField.barterId] ?? "";

  int? get transactionTs => extras[ExtraReservedField.transactionTs];

  Manifest.barter(this.category, String contentId, String barterId,
      {Map<String, dynamic> extras = const <String, dynamic>{}, this.details = const <Details>[]})
      : assert(contentId.isNotEmpty),
        assert(barterId.isNotEmpty),
        extras = {
          ...extras,
          ...{ExtraReservedField.contentId: contentId, ExtraReservedField.barterId: barterId}
        };

  Manifest.action(this.category, String scene,
      {Map<String, dynamic> extras = const <String, dynamic>{}, this.details = const <Details>[]})
      : assert(scene.isNotEmpty),
        extras = {
          ...extras,
          ...{ExtraReservedField.scene: scene}
        };

  const Manifest(this.category,
      {this.extras = const <String, dynamic>{}, this.details = const <Details>[]});

  Manifest offer(String offerId, String basePlanId) {
    return Manifest(category,
        extras: {
          ...extras,
          ...{ExtraReservedField.offerId: offerId, ExtraReservedField.basePlanId: basePlanId}
        },
        details: details);
  }

  factory Manifest.fromJson(Map<String, dynamic> json) => _$ManifestFromJson(json);

  static const Manifest empty = Manifest("empty");

  Map<String, dynamic> toJson() => _$ManifestToJson(this);
}

extension DefaultManifestExtension on Manifest {
  int get igcAmount => details
      .map((details) => details.type == DetailsReservedType.igc ? details.amount : 0)
      .reduce((value, element) => element + value);
}
