import 'package:guru_app/financial/product/product_model.dart';
import 'package:guru_utils/converts/converts.dart';
import 'package:guru_utils/log/log.dart';

/// Created by Haoyi on 3/4/21
import 'package:json_annotation/json_annotation.dart';


part 'in_app_receipt_ios.g.dart';

enum SubscriptionType { autoRenewable, nonRenewing }

class SubscriptionPeriod {
  final Duration? period;
  final Duration? trial;

  @override
  String toString() {
    return 'SubscriptionPeriod{period: $period, trial: $trial}';
  }

  SubscriptionPeriod(this.period, this.trial);
}

class IosReceiptStatus {
  /// Not decodable status
  static const unknown = -2;

  /// No status returned
  static const none = -1;

  /// valid statua
  static const valid = 0;

  /// The App Store could not read the JSON object you provided.
  static const jsonNotReadable = 21000;

  /// The data in the receipt-data property was malformed or missing.
  static const malformedOrMissingData = 21002;

  /// The receipt could not be authenticated.
  static const receiptCouldNotBeAuthenticated = 21003;

  /// The shared secret you provided does not match the shared secret on file for your account.
  static const secretNotMatching = 21004;

  /// The receipt server is not currently available.
  static const receiptServerUnavailable = 21005;

  /// This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.
  static const subscriptionExpired = 21006;

  ///  This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.
  static const testReceipt = 21007;

  /// This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.
  static const productionEnvironment = 21008;

  bool isValid(int status) {
    return status == IosReceiptStatus.valid;
  }
}

class ReceiptResult {
  final int status;
  final int expiredDateInMillis;
  // final List<ReceiptItem> items;

  ReceiptResult(this.status, {this.expiredDateInMillis = 0});

  @override
  String toString() {
    return 'SubscriptionResult{state: $status, expiredDateInMillis: $expiredDateInMillis}';
  }
}

@JsonSerializable()
class ReceiptData {
  @JsonKey(name: "environment")
  final String? environment;

  @JsonKey(name: "receipt")
  final Receipt? receipt;

  @JsonKey(name: "latest_receipt_info", defaultValue: <ReceiptItem>[])
  final List<ReceiptItem>? latestReceiptItems;

  @JsonKey(name: "pending_renewal_info", defaultValue: <PendingRenewalInfo>[])
  final List<PendingRenewalInfo>? pendingRenewalInfoItems;

  @JsonKey(name: "latest_receipt", defaultValue: "")
  final String? latestReceipt;

  @JsonKey(name: "status")
  final int status;

  int get requestDateInMillis => receipt?.requestDateInMillis ?? 0;

  ReceiptData({this.environment, this.receipt, this.latestReceiptItems, this.pendingRenewalInfoItems, this.latestReceipt, required this.status});

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

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

  dumpLog() {
    Log.d("========= RECEIPT DATE ========");
    Log.d("  [status] => $status");
    Log.d("  [receipt begin] ========>");
    receipt?.dumpLog();
    Log.d("  <======== [receipt end] ");
    Log.d("  [latestReceiptItems] => ${latestReceiptItems?.length}");
    Log.d("  [pendingRenewalInfoItems] => ${pendingRenewalInfoItems?.length}");
    Log.d("  [latestReceipt] => $latestReceipt");
    Log.d("========= RECEIPT DATE ========");
  }

  List<ReceiptItem> getCheckReceipts({SubscriptionType type = SubscriptionType.autoRenewable, List<ProductId> checkIds = const []}) {
    final List<ReceiptItem> receipts = <ReceiptItem>[];
    final Set<String> platformCheckIds = checkIds.map((id) => id.sku).toSet();
    switch (type) {
      case SubscriptionType.nonRenewing:
        if (receipt?.inAppReceiptItems != null && receipt!.inAppReceiptItems.isNotEmpty) {
          receipts.addAll(receipt!.inAppReceiptItems);
        }
        break;
      default:
        if (latestReceiptItems != null && latestReceiptItems!.isNotEmpty) {
          receipts.addAll(latestReceiptItems!);
        }
        break;
    }
    dumpLog();
    Log.d("[validate]==> receipts.length:${receipts.length}");
    if (checkIds.isNotEmpty == true) {
      return receipts.where((receipt) {
        Log.d("[validate]==> productId:${receipt.productId} ${platformCheckIds.contains(receipt.productId)}");
        return platformCheckIds.contains(receipt.productId);
      }).toList();
    }
    return <ReceiptItem>[];
  }
}

@JsonSerializable()
class Receipt {
  @JsonKey(name: "receipt_type")
  final String receiptType;

  @JsonKey(name: "adam_id")
  final int adamId;

  @JsonKey(name: "app_item_id")
  final int appItemId;

  @JsonKey(name: "bundle_id")
  final String bundleId;

  @JsonKey(name: "application_version")
  final String applicationVersion;

  @JsonKey(name: "download_id")
  final int downloadId;

  @JsonKey(name: "version_external_identifier")
  final int versionExternalIdentifier;

  @JsonKey(name: "receipt_creation_date_ms")
  @intStringConvert
  final int receiptCreationDateInMillis;

  @JsonKey(name: "request_date_ms")
  @intStringConvert
  final int requestDateInMillis;

  @JsonKey(name: "original_purchase_date_ms")
  @intStringConvert
  final int originalPurchaseDateInMillis;

  @JsonKey(name: "original_application_version")
  final String originalApplicationVersion;

  @JsonKey(name: "in_app", defaultValue: <ReceiptItem>[])
  final List<ReceiptItem> inAppReceiptItems;

  Receipt(
      {required this.receiptType,
        required this.adamId,
        required this.appItemId,
        required this.bundleId,
        required this.applicationVersion,
        required this.downloadId,
        required this.versionExternalIdentifier,
        required this.receiptCreationDateInMillis,
        required this.requestDateInMillis,
        required this.originalPurchaseDateInMillis,
        required this.originalApplicationVersion,
        required this.inAppReceiptItems});

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

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

  void dumpLog() {
    Log.d("    [receiptType] => $receiptType");
    Log.d("    [adamId] => $adamId");
    Log.d("    [appItemId] => $appItemId");
    Log.d("    [bundleId] => $bundleId");
    Log.d("    [applicationVersion] => $applicationVersion");
    Log.d("    [downloadId] => $downloadId");
    Log.d("    [versionExternalIdentifier] => $versionExternalIdentifier");
    Log.d("    [receiptCreationDateInMillis] => $receiptCreationDateInMillis");
    Log.d("    [requestDateInMillis] => $requestDateInMillis");
    Log.d("    [originalPurchaseDateInMillis] => $originalPurchaseDateInMillis");
    Log.d("    [originalApplicationVersion] => $originalApplicationVersion");
    Log.d("    [inAppReceiptItems] => ${inAppReceiptItems.length}");
  }
}

@JsonSerializable()
class ReceiptItem {
  @JsonKey(name: "product_id")
  final String productId;

  @JsonKey(name: "quantity")
  @intStringConvert
  final int quantity;

  @JsonKey(name: "transaction_id")
  final String transactionId;

  @JsonKey(name: "original_transaction_id")
  final String originalTransactionId;

  @JsonKey(name: "purchase_date_ms")
  @intStringConvert
  final int purchaseDateInMillis;

  @JsonKey(name: "expires_date_ms")
  @intStringConvert
  final int? expiresDateInMillis;

  @JsonKey(name: "expires_date")
  final String? expiresDateInString;

  @JsonKey(name: "original_purchase_date_ms")
  @intStringConvert
  final int originalPurchaseDateInMillis;

  @JsonKey(name: "web_order_line_item_id")
  final String? webOrderLineItemId;

  @JsonKey(name: "is_trial_period")
  @boolStringConvert
  final bool isTrialPeriod;

  @JsonKey(name: "is_in_intro_offer_period")
  @boolStringConvert
  final bool? isInIntroOfferPeriod;

  @JsonKey(name: "subscription_group_identifier")
  final String? subscriptionGroupIdentifier;

  @JsonKey(name: "cancellation_date")
  final String? cancellationDate;

  ReceiptItem(
      {required this.productId,
        required this.quantity,
        required this.transactionId,
        required this.originalTransactionId,
        required this.purchaseDateInMillis,
        required this.expiresDateInMillis,
        required this.originalPurchaseDateInMillis,
        required this.webOrderLineItemId,
        required this.isTrialPeriod,
        required this.isInIntroOfferPeriod,
        required this.subscriptionGroupIdentifier,
        required this.cancellationDate,
        this.expiresDateInString});

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

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

@JsonSerializable()
class PendingRenewalInfo {
  // @JsonKey(name: "expiration_intent")
  // @INT_STRING_CONVERT
  // final int expirationIntent;

  @JsonKey(name: "auto_renew_product_id")
  final String autoRenewProductId;

  @JsonKey(name: "original_transaction_id")
  final String originalTransactionId;

  // @JsonKey(name: "is_in_billing_retry_period")
  // @INT_STRING_CONVERT
  // final int isInBillingRetryPeriod;

  @JsonKey(name: "product_id")
  final String productId;

  @JsonKey(name: "auto_renew_status")
  @intStringConvert
  final int autoRenewStatus;

  PendingRenewalInfo(
      {
        required this.autoRenewProductId,
        required this.originalTransactionId,
        required this.productId,
        required this.autoRenewStatus});

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

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