package com.atmob.voiceai.sdk.billing;


import android.app.Activity;
import android.app.Application;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;

import com.android.billingclient.api.AccountIdentifiers;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.PendingPurchasesParams;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.QueryPurchasesParams;
import com.atmob.common.logging.AtmobLog;
import com.atmob.common.runtime.ProcessUtil;
import com.atmob.voiceai.sdk.billing.bean.GPProductInfo;
import com.atmob.voiceai.sdk.billing.operation.Purchases;
import com.atmob.voiceai.utils.ToastUtil;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

import javax.inject.Inject;
import javax.inject.Singleton;

import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import atmob.reactivex.rxjava3.core.Observable;
import atmob.reactivex.rxjava3.core.ObservableSource;
import atmob.reactivex.rxjava3.core.Observer;
import atmob.reactivex.rxjava3.core.Single;
import atmob.reactivex.rxjava3.core.SingleSource;
import atmob.reactivex.rxjava3.disposables.Disposable;
import atmob.reactivex.rxjava3.functions.Function;
import atmob.reactivex.rxjava3.schedulers.Schedulers;

@Singleton
public class GPBillingClient implements PurchasesUpdatedListener {

    private static final String TAG = GPBillingClient.class.getSimpleName();

    private final int BILLING_CLIENT_CONNECTED_TIMEOUT = 5000;
    private final int TRY_CON_MAX_POW_TIMES = 3;

    private final int TRY_URGENT_CON_TIMES = 300;

    private final int TRY_URGENT_DELAY_TIME_MILLISECONDS = 200;

    private final BillingClient billingClient;

    private Disposable powConnectionDisposable;
    private Disposable urgentConnectionDisposable;

    private final ThreadPoolExecutor threadPoolExecutor;

    private final ReentrantLock lock = new ReentrantLock();

    private Runnable urgentConnectionRunnable;

    private final BillingStrategy billingStrategy;

    private final HashMap<Object, HashMap<String, BillingFlowListener>> billingFlowListenerMap = new HashMap<>();

    @Inject
    public GPBillingClient(Application context) {
        billingClient = BillingClient.newBuilder(context)
                .setListener(this)
                .enablePendingPurchases(PendingPurchasesParams.newBuilder().enablePrepaidPlans().enableOneTimeProducts().build())
                .build();
        billingStrategy = new BillingStrategy(billingClient);
        threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
        addApplicationLifecycleListener();
    }


    /**
     * 开启间隔时间较短的连接操作
     */
    public void startUrgentConnection() {
        urgentConnectionRunnable = () -> {
            if (billingClient.isReady()) {
                return;
            }
            doUrgentConnection();
        };
        threadPoolExecutor.execute(urgentConnectionRunnable);
    }

    /**
     * 关闭连接操作
     */
    public void endUrgentConnection() {
        AtmobLog.d(TAG, "End connection.——Urgent");
        if (urgentConnectionDisposable != null && !urgentConnectionDisposable.isDisposed()) {
            urgentConnectionDisposable.dispose();
        }
        while (lock.getHoldCount() > 0) {
            lock.unlock();
        }
        threadPoolExecutor.remove(urgentConnectionRunnable);
    }


    private void doUrgentConnection() {
        Observable.create(emitter -> {
                    billingClient.startConnection(new BillingClientStateListener() {
                        @Override
                        public void onBillingServiceDisconnected() {
                            AtmobLog.e(TAG, "onBillingServiceDisconnected() called.——Urgent");
                            emitter.onError(new RuntimeException("Billing service disconnected.——Urgent"));
                        }

                        @Override
                        public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                            AtmobLog.d(TAG, "onBillingSetupFinished() called with: billingResult = [" + billingResult + "]——Urgent");
                            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                                emitter.onNext(billingResult);
                                emitter.onComplete();
                            } else {
                                emitter.onError(new RuntimeException("Billing setup failed.——Urgent"));
                            }
                        }
                    });
                })
                .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {

                    private final AtomicInteger count = new AtomicInteger(0);

                    @Override
                    public ObservableSource<?> apply(Observable<Throwable> throwableObservable) {
                        return throwableObservable.takeWhile(throwable -> {
                                    if (count.getAndIncrement() < TRY_URGENT_CON_TIMES) {
                                        return true;
                                    }
                                    throw throwable;
                                })
                                .flatMap((Function<Throwable, ObservableSource<?>>) throwable -> Observable.timer(TRY_URGENT_DELAY_TIME_MILLISECONDS, TimeUnit.MILLISECONDS));
                    }
                })

                .subscribe(new Observer<Object>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                        urgentConnectionDisposable = d;
                        lock.lock();
                    }

                    @Override
                    public void onNext(@NonNull Object o) {

                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        AtmobLog.d(TAG, "Billing connection retry times exceed max retry times, stop retry.——Urgent");
                    }

                    @Override
                    public void onComplete() {
                        AtmobLog.d(TAG, "Billing connection success.——Urgent");
                        lock.unlock();
                    }
                });
    }


    /**
     * 应用启动时尝试连接 billing client
     */
    private void addApplicationLifecycleListener() {
        ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() {

            @Override
            public void onCreate(@NonNull LifecycleOwner owner) {
                if (!billingClient.isReady()) {
                    AtmobLog.d(TAG, "Billing client is not ready, start connection.");
                    startPowConnection();
                }
            }

            @Override
            public void onDestroy(@NonNull LifecycleOwner owner) {
                endPowConnection();
            }
        });
    }

    private void endPowConnection() {
        AtmobLog.d(TAG, "End connection.");
        if (billingClient.isReady()) {
            AtmobLog.d(TAG, "Billing client is ready, end connection.");
            billingClient.endConnection();
        }
        if (powConnectionDisposable != null && !powConnectionDisposable.isDisposed()) {
            powConnectionDisposable.dispose();
        }
    }

    private void startPowConnection() {
        Observable.create(emitter -> {
                    billingClient.startConnection(new BillingClientStateListener() {
                        @Override
                        public void onBillingServiceDisconnected() {
                            AtmobLog.d(TAG, "Billing service disconnected.");
                            emitter.onError(new RuntimeException("Billing service disconnected."));
                        }

                        @Override
                        public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                            AtmobLog.d(TAG, "Billing setup finished with result: " + billingResult.getResponseCode() + " " + billingResult.getDebugMessage());
                            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                                emitter.onNext(billingResult);
                                emitter.onComplete();
                            } else {
                                emitter.onError(new RuntimeException("Billing setup failed."));
                            }
                        }
                    });
                })
                .retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {

                    private final AtomicInteger count = new AtomicInteger(0);

                    @Override
                    public ObservableSource<?> apply(Observable<Throwable> throwableObservable) {
                        return throwableObservable.takeWhile(throwable -> {
                                    if (count.getAndIncrement() < TRY_CON_MAX_POW_TIMES) {
                                        return true;
                                    }
                                    throw throwable;
                                })
                                .flatMap((Function<Throwable, ObservableSource<?>>) throwable -> {
                                    int i = count.get();
                                    int delay = (int) Math.pow(2, i);
                                    return Observable.timer(delay, TimeUnit.SECONDS);
                                });
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Object>() {
                    @Override
                    public void onSubscribe(@NonNull Disposable d) {
                        powConnectionDisposable = d;
                    }

                    @Override
                    public void onNext(@NonNull Object o) {

                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        AtmobLog.d(TAG, "Billing connection retry times exceed max retry times, stop retry.");
                    }

                    @Override
                    public void onComplete() {
                        AtmobLog.d(TAG, "Billing connection success.");
                    }
                });
    }


    @Override
    public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {
        AtmobLog.d(TAG, "onPurchasesUpdated() called with: billingResult = [" + billingResult + "], list = [" + list + "]");
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
            for (Purchase purchase : list) {
                AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();
                if (accountIdentifiers == null) {
                    continue;
                }
                String obfuscatedAccountId = accountIdentifiers.getObfuscatedAccountId();
                Iterator<Map.Entry<Object, HashMap<String, BillingFlowListener>>> iterator = billingFlowListenerMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<Object, HashMap<String, BillingFlowListener>> next = iterator.next();
                    HashMap<String, BillingFlowListener> value = next.getValue();
                    BillingFlowListener billingFlowListener = value.get(obfuscatedAccountId);
                    if (billingFlowListener != null) {
                        billingFlowListener.onSuccess(purchase);
                        iterator.remove();
                    }
                }
            }
        }
        billingFlowListenerMap.clear();
    }


    public Single<List<Purchase>> queryPurchase(@BillingClient.ProductType String productType) {
        return Single.fromCallable(() -> {
                    if (ProcessUtil.isMainThread()) {
                        throw new IllegalStateException("queryPurchase must not be called on the main thread");
                    }
                    if (lock.tryLock(BILLING_CLIENT_CONNECTED_TIMEOUT, TimeUnit.MILLISECONDS)) {
                        lock.unlock();
                        return true;
                    } else {
                        throw new TimeoutException();
                    }
                })
                .flatMap((Function<Boolean, SingleSource<List<Purchase>>>) aBoolean
                        -> billingStrategy.queryPurchase(productType));
    }


    public Single<BillingResult> subscriptionGoods(Activity activity, Object productDetails, String basePlanId, String orderNo) {
        return Single.fromCallable(() -> {
                    if (ProcessUtil.isMainThread()) {
                        throw new IllegalStateException("launchBillingFlow must not be called on the main thread");
                    }
                    if (lock.tryLock(BILLING_CLIENT_CONNECTED_TIMEOUT, TimeUnit.MILLISECONDS)) {
                        lock.unlock();
                        return true;
                    } else {
                        throw new TimeoutException();
                    }
                })
                .flatMap((Function<Boolean, SingleSource<BillingResult>>) aBoolean
                        -> billingStrategy.subscriptionGoods(activity, productDetails, basePlanId, orderNo))
                .map(billingResult -> {
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                        return billingResult;
                    } else {
                        throw new IllegalStateException("launchBillingFlow failed billingResult: " + billingResult);
                    }
                });
    }


    public Single<GPProductInfo> querySkuDetails(String productType, @NonNull String productId, @Nullable String basePlanId, @Nullable String legacyProductId) {
        return Single.fromCallable(() -> {
                    if (ProcessUtil.isMainThread()) {
                        throw new IllegalStateException("querySkuDetails must not be called on the main thread");
                    }
                    if (lock.tryLock(BILLING_CLIENT_CONNECTED_TIMEOUT, TimeUnit.MILLISECONDS)) {
                        lock.unlock();
                        return true;
                    } else {
                        throw new TimeoutException();
                    }
                })
                .flatMap((Function<Boolean, SingleSource<GPProductInfo>>) aBoolean
                        -> billingStrategy.queryGoodsDetails(productType, productId, basePlanId, legacyProductId));
    }


    public void registerFlowListener(Object tag, String orderId, BillingFlowListener listener) {
        HashMap<String, BillingFlowListener> map = billingFlowListenerMap.get(tag);
        if (map == null) {
            map = new HashMap<>();
            billingFlowListenerMap.put(tag, map);
        }
        map.put(orderId, listener);
    }

    public void unregisterAllFlowListener(Object tag) {
        billingFlowListenerMap.remove(tag);
    }

    public interface BillingFlowListener {
        void onSuccess(Purchase purchase);
    }
}
