package com.makecdev.coderemotemanager;

import android.app.Activity;
import com.android.billingclient.api.*;
import com.getcapacitor.*;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.json.JSONObject;

@CapacitorPlugin(name = "ProBilling")
public final class ProBillingPlugin extends Plugin {
    private static final String PRODUCT_ID = "pro_multi_account";
    private static final ExecutorService VERIFIER_EXECUTOR = Executors.newSingleThreadExecutor();
    private ProBillingClient client;

    @Override public void load() {
        GoogleStore store = new GoogleStore(getActivity());
        client = new ProBillingClient(store, new HttpVerifier(BuildConfig.ENTITLEMENT_VERIFIER_URL));
    }

    @PluginMethod public void getProduct(PluginCall call) {
        client.getProduct(value -> call.resolve(new JSObject()
            .put("productId", value.productId).put("localizedPrice", value.localizedPrice).put("available", value.available)));
    }

    @PluginMethod public void purchase(PluginCall call) { runBound(call, true); }
    @PluginMethod public void restore(PluginCall call) { runBound(call, false); }

    private void runBound(PluginCall call, boolean purchase) {
        String thumbprint = call.getString("backendKeyThumbprint");
        String challenge = call.getString("challengeHash");
        if (!validBinding(thumbprint) || !validBinding(challenge)) { call.reject("invalid_binding"); return; }
        ProBillingClient.Binding binding = new ProBillingClient.Binding(thumbprint, challenge);
        ProBillingClient.Callback<ProBillingClient.Result> callback = result -> {
            JSObject output = new JSObject().put("state", result.state);
            if (result.certificate != null) output.put("certificate", result.certificate).put("expiresAt", result.expiresAt);
            call.resolve(output);
        };
        if (purchase) client.purchase(getActivity(), binding, callback); else client.restore(binding, callback);
    }

    private static boolean validBinding(String value) { return value != null && value.matches("[A-Za-z0-9_-]{43}"); }

    private static final class GoogleStore implements ProBillingClient.Store, PurchasesUpdatedListener {
        private final Activity activity;
        private final BillingClient billing;
        private ProductDetails latest;
        private ProBillingClient.Callback<ProBillingClient.Purchase> purchaseCallback;
        GoogleStore(Activity activity) {
            this.activity = activity;
            billing = BillingClient.newBuilder(activity).setListener(this).enablePendingPurchases(
                PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()).build();
        }
        public void connect(ProBillingClient.Callback<Boolean> callback) {
            if (billing.isReady()) { callback.accept(true); return; }
            billing.startConnection(new BillingClientStateListener() {
                public void onBillingSetupFinished(BillingResult result) { callback.accept(result.getResponseCode() == BillingClient.BillingResponseCode.OK); }
                public void onBillingServiceDisconnected() { }
            });
        }
        public void queryProduct(ProBillingClient.Callback<ProBillingClient.Product> callback) {
            QueryProductDetailsParams.Product query = QueryProductDetailsParams.Product.newBuilder()
                .setProductId(PRODUCT_ID).setProductType(BillingClient.ProductType.INAPP).build();
            billing.queryProductDetailsAsync(QueryProductDetailsParams.newBuilder().setProductList(List.of(query)).build(), (result, detailsResult) -> {
                if (result.getResponseCode() != BillingClient.BillingResponseCode.OK || detailsResult.getProductDetailsList().size() != 1) { callback.accept(null); return; }
                latest = detailsResult.getProductDetailsList().get(0);
                List<ProductDetails.OneTimePurchaseOfferDetails> offers = latest.getOneTimePurchaseOfferDetailsList();
                if (offers == null || offers.isEmpty()) { callback.accept(null); return; }
                ProductDetails.OneTimePurchaseOfferDetails offer = offers.get(0);
                callback.accept(new ProBillingClient.Product(latest.getProductId(), offer.getFormattedPrice(), offer.getOfferToken()));
            });
        }
        public int launch(Object ignored, ProBillingClient.Product product, ProBillingClient.Callback<ProBillingClient.Purchase> callback) {
            if (latest == null) return -1;
            purchaseCallback = callback;
            BillingFlowParams.ProductDetailsParams detail = BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(latest).setOfferToken(product.offerToken).build();
            BillingResult result = billing.launchBillingFlow(activity, BillingFlowParams.newBuilder().setProductDetailsParamsList(List.of(detail)).build());
            return mapCode(result.getResponseCode());
        }
        public void queryOwned(ProBillingClient.Callback<ProBillingClient.Purchase> callback) {
            billing.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(),
                (result, purchases) -> callback.accept(result.getResponseCode() == BillingClient.BillingResponseCode.OK ? normalize(matchingPurchase(purchases)) : null));
        }
        public void onPurchasesUpdated(BillingResult result, List<Purchase> purchases) {
            ProBillingClient.Callback<ProBillingClient.Purchase> callback = purchaseCallback;
            purchaseCallback = null;
            if (callback == null) return;
            if (result.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) callback.accept(new ProBillingClient.Purchase(ProBillingClient.CANCELED, null, false));
            else callback.accept(result.getResponseCode() == BillingClient.BillingResponseCode.OK ? normalize(matchingPurchase(purchases)) : null);
        }
        private static Purchase matchingPurchase(List<Purchase> purchases) {
            if (purchases == null) return null;
            for (Purchase purchase : purchases) if (purchase.getProducts().contains(PRODUCT_ID)) return purchase;
            return null;
        }
        private static ProBillingClient.Purchase normalize(Purchase purchase) {
            if (purchase == null) return null;
            int state = purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED ? ProBillingClient.PURCHASED
                : purchase.getPurchaseState() == Purchase.PurchaseState.PENDING ? ProBillingClient.PENDING : ProBillingClient.CANCELED;
            return new ProBillingClient.Purchase(state, state == ProBillingClient.PURCHASED ? purchase.getPurchaseToken() : null, purchase.isAcknowledged());
        }
        private static int mapCode(int code) {
            if (code == BillingClient.BillingResponseCode.OK) return ProBillingClient.OK;
            if (code == BillingClient.BillingResponseCode.USER_CANCELED) return ProBillingClient.CANCELED;
            if (code == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) return ProBillingClient.ALREADY_OWNED;
            return -1;
        }
    }

    private static final class HttpVerifier implements ProBillingClient.Verifier {
        private final String baseUrl;
        HttpVerifier(String baseUrl) { this.baseUrl = baseUrl; }
        public void verify(String token, ProBillingClient.Binding binding, ProBillingClient.Callback<ProBillingClient.Certificate> callback) {
            VERIFIER_EXECUTOR.execute(() -> {
                ProBillingClient.Certificate certificate = null;
                try {
                    if (!baseUrl.startsWith("https://")) throw new IOException();
                    HttpURLConnection connection = (HttpURLConnection) new URL(baseUrl + "/v1/entitlements/google-play/verify").openConnection();
                    connection.setRequestMethod("POST"); connection.setConnectTimeout(10_000); connection.setReadTimeout(10_000);
                    connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/json");
                    JSONObject body = new JSONObject().put("purchaseToken", token)
                        .put("backendKeyThumbprint", binding.backendKeyThumbprint).put("challengeHash", binding.challengeHash);
                    try (OutputStream output = connection.getOutputStream()) { output.write(body.toString().getBytes(StandardCharsets.UTF_8)); }
                    if (connection.getResponseCode() == 200) {
                        String response;
                        try (InputStream input = connection.getInputStream()) { response = new String(input.readAllBytes(), StandardCharsets.UTF_8); }
                        JSONObject json = new JSONObject(response);
                        certificate = new ProBillingClient.Certificate(json.getString("certificate"), json.getString("expiresAt"));
                    }
                } catch (Exception ignored) { }
                ProBillingClient.Certificate result = certificate;
                activityRun(callback, result);
            });
        }
        private static void activityRun(ProBillingClient.Callback<ProBillingClient.Certificate> callback, ProBillingClient.Certificate value) {
            new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> callback.accept(value));
        }
    }
}
