/*******************************************************
 * Copyright (C) 2018 XMind Ltd. - All Rights Reserved
 *******************************************************/

#import <StoreKit/StoreKit.h>
#import <napi.h>
#import <string>

static NSMutableSet* _purchaseCache = nil;
static NSMutableSet* getPurchaseCache() {
  if (_purchaseCache == nil) {
    _purchaseCache = [[NSMutableSet alloc] init];
  }
  return _purchaseCache;
}

// 辅助函数：将 N-API 字符串转换为 NSString
inline NSString* PurchaseToNSString(const std::string& value) {
  return [[NSString alloc] initWithUTF8String:value.c_str()];
}

@interface VanaInAppPurchase : NSObject <SKProductsRequestDelegate> {
  @private
    Napi::ThreadSafeFunction tsfn;
    NSInteger quantity_;
    NSString* errorMessage;
    bool isValidProduct;
}

- (id)initWithCallback:(Napi::ThreadSafeFunction)callback quantity:(NSInteger)quantity;
@end

@implementation VanaInAppPurchase

- (id)initWithCallback:(Napi::ThreadSafeFunction)callback quantity:(NSInteger)quantity {
  if ((self = [super init])) {
    tsfn = std::move(callback);
    quantity_ = quantity;

    // Add the product into cache, otherwise, it will be ARC-ed.
    NSMutableSet* cache = getPurchaseCache();
    @synchronized(cache) {
      [cache addObject:self];
    }
  }
  return self;
}

- (void)purchaseProduct:(NSString*)productID {
  isValidProduct = false;

  SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:
    [NSSet setWithObject:productID]
  ];

  productsRequest.delegate = self;
  [productsRequest start];
}

- (void)productsRequest:(SKProductsRequest*)request didReceiveResponse:(SKProductsResponse*)response {
  // Get the products.
  SKProduct* product = [response.products count] == 1 ? [response.products firstObject] : nil;

  if (product == nil) {
    errorMessage = @"InvalidProduct";
    isValidProduct = false;
  } else {
    SKMutablePayment* payment = [SKMutablePayment paymentWithProduct:product];
    payment.quantity = quantity_;
    [[SKPaymentQueue defaultQueue] addPayment:payment];

    isValidProduct = true;
  }

  auto callback = [isValid = isValidProduct, error = errorMessage]
                  (Napi::Env env, Napi::Function jsCallback) {
    if (isValid) {
      jsCallback.Call({Napi::Boolean::New(env, true)});
    } else {
      jsCallback.Call({
        Napi::Boolean::New(env, false),
        Napi::String::New(env, [error UTF8String])
      });
    }
  };


  tsfn.BlockingCall(callback);
  tsfn.Release();


  // 清理
  NSMutableSet* cache = getPurchaseCache();
  @synchronized(cache) {
    [cache removeObject:self];
  }
}

- (void)request:(SKRequest*)request didFailWithError:(NSError*)error {
  errorMessage = [error localizedDescription];
  isValidProduct = false;

  auto callback = [error = errorMessage]
                  (Napi::Env env, Napi::Function jsCallback) {
    jsCallback.Call({
      Napi::Boolean::New(env, false),
      Napi::String::New(env, [error UTF8String])
    });
  };


  tsfn.BlockingCall(callback);
  tsfn.Release();

  // 清理
  NSMutableSet* cache = getPurchaseCache();
  @synchronized(cache) {
    [cache removeObject:self];
  }
}
@end

Napi::Value IapPurchaseProduct(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  // 参数验证
  if (info.Length() < 1 || !info[0].IsString()) {
    Napi::TypeError::New(env, "Invalid arguments").ThrowAsJavaScriptException();
    return env.Undefined();
  }

  std::string productId = info[0].As<Napi::String>().Utf8Value();
  int quantity = 1;
  Napi::ThreadSafeFunction tsfn;

  // 处理可选参数
  if (info.Length() > 1) {
    if (info[1].IsNumber()) {
      quantity = info[1].As<Napi::Number>().Int32Value();
    }
    if (info[1].IsFunction()) {
      tsfn = Napi::ThreadSafeFunction::New(
        env,
        info[1].As<Napi::Function>(),
        "IAPCallback",
        0,
        1
      );
    }
    if (info.Length() > 2 && info[2].IsFunction()) {
      tsfn = Napi::ThreadSafeFunction::New(
        env,
        info[2].As<Napi::Function>(),
        "IAPCallback",
        0,
        1
      );
    }
  }

  @autoreleasepool {
    VanaInAppPurchase* purchase = [[VanaInAppPurchase alloc]
                                   initWithCallback:std::move(tsfn)
                                   quantity:quantity];
    [purchase purchaseProduct:PurchaseToNSString(productId)];
  }

  return env.Undefined();
}