package com.vntel.cccd;

import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerModule;
import com.htc.sdk.model.CardResult;
import com.htc.sdk.model.IDCardDetail;
import com.htc.sdk.model.MRZInfo;
import com.htc.sdk.model.MRZResult;
import com.vntel.cccd.processor.NFCProcessor;
import com.vntel.cccd.processor.NFCProcessorDelegate;

import org.json.JSONObject;

import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

public class RNVntelCCCDModule extends ReactContextBaseJavaModule implements LifecycleEventListener, RNVntelCCCDMRZViewDelegate {

    private final static String TAG = "RNVntelCCCD";
    private final static int MRZ_SCAN_REQUEST_CODE = 99;
    private final static int NFC_SCAN_REQUEST_CODE = 100;
    private final ReactApplicationContext reactContext;
    private List<Integer> mrzViews = Collections.synchronizedList(new ArrayList<Integer>());
    private NfcAdapter mAdapter;
    private ScanInfo scanInfo = new ScanInfo();

    private class ScanInfo {
        private MRZResult mrz;
        private IDCardDetail cardDetails;

        public void clean() {
            mrz = null;
            cardDetails = null;
        }
        public MRZResult getMRZ() {
            return mrz;
        }

        public void setMRZ(MRZResult mrz) {
            this.mrz = mrz;
        }

        public IDCardDetail getCardDetails() {
            return cardDetails;
        }

        public void setCardDetails(IDCardDetail cardDetails) {
            this.cardDetails = cardDetails;
        }
    }
    private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
        @Override
        public void onNewIntent(Intent intent) {
            if(intent.getAction().equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
                handleNFC(intent);
            }
            super.onNewIntent(intent);
        }

        @Override
        public void onActivityResult(Activity activity, int requestCode, int resultCode, final Intent intent) {
            if (requestCode == MRZ_SCAN_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
                UiThreadUtil.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        try {

                        } catch (Exception ex) {
                            Log.e(TAG, ex.getMessage());
                        }
                    }
                });
            } else if (requestCode == NFC_SCAN_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
                handleNFC(intent);
            }
        }
    };
    private void handleNFC(Intent intent) {
        UiThreadUtil.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    MRZResult mrz = scanInfo.getMRZ();
                    if (mrz != null) {
                        sendEvent("ScanEvent", "NFC_SCAN_BEGIN", null);
                        new NFCProcessor(intent, mrz.getCccdId(), new NFCProcessorDelegate() {
                            @Override
                            public void onSuccess(CardResult result) {
                                try {
                                    WritableMap object = new WritableNativeMap();
                                    object.putString("com", "YBhfAQQwMTA3XzYGMDQwMDAwXAZhdWNtb24=");
                                    object.putString("sod", result.getBase64SOD().replace("\n", "").replace("\r", ""));
                                    for(int idx = 1; idx <= 16; idx++) {
                                        String dg = result.getBase64DG(idx);
                                        if (dg != null) {
                                            object.putString("dg" + idx, dg.replace("\n", "").replace("\r", ""));
                                        }
                                    }
                                    sendEvent("ScanEvent", "NFC_SCAN_FINISH", object);
                                } catch (Exception ex) {
                                    WritableMap object = new WritableNativeMap();
                                    object.putString("error", ex.toString());
                                    sendEvent("ScanEvent", "NFC_SCAN_ERROR", object);
                                }

                            }

                            @Override
                            public void onFailure(Exception ex) {
                                WritableMap object = new WritableNativeMap();
                                object.putString("error", ex.toString());
                                sendEvent("ScanEvent", "NFC_SCAN_ERROR", object);
                            }
                        }).execute();
                    }

                } catch (Exception ex) {
                    Log.e(TAG, ex.getMessage());
                }
            }
        });
    }

    public RNVntelCCCDModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        reactContext.addLifecycleEventListener(this);
        reactContext.addActivityEventListener(mActivityEventListener);
    }

    void sendEvent(String name, String event, ReadableMap payload) {
        WritableMap params = Arguments.createMap();
        params.putString("event", event);
        if(payload != null) {
            params.putMap("payload", payload);
        }


        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(name, params);
    }

    @Override
    public String getName() {
        return TAG;
    }

    @ReactMethod
    public void addListener(String eventName) {
        // Keep: Required for RN built in Event Emitter Calls.
    }
    @ReactMethod
    public void removeListeners(Integer count) {
        // Keep: Required for RN built in Event Emitter Calls.
    }

    private void updateMRZView() {
        UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);

        uiManager.addUIBlock(new UIBlock() {
            @Override
            public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
                synchronized (mrzViews) {
                    Log.i(TAG, "updateMRZView");

                    Iterator<Integer> iterator = mrzViews.iterator();
                    while (iterator.hasNext()) {
                        final int tagId = iterator.next();
                        try {
                            final RNVntelCCCDMRZView view = (RNVntelCCCDMRZView) nativeViewHierarchyManager.resolveView(tagId);
                            if (view != null) view.update(RNVntelCCCDModule.this);
                        } catch (Exception ex) {
                            Log.e(TAG, ex.getMessage());
                        }
                    }
                }
            }
        });
    }
    @ReactMethod
    public void isInitialized(final Promise promise) {
        UiThreadUtil.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    promise.resolve(true);
                } catch (Exception ex) {
                    promise.reject("ERR_UNEXPECTED_EXCEPTION", ex);
                }
            }
        });
    }

    @ReactMethod
    public void initialize(final ReadableMap params, final ReadableMap settings, final Promise promise) {
        UiThreadUtil.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Log.i(TAG, "initialize");

                } catch (Exception ex) {
                    promise.reject("ERR_UNEXPECTED_EXCEPTION", ex);
                }
            }
        });
    }

    @Override
    public void onHostResume() {
        Log.i(TAG, "onHostResume");
    }

    @Override
    public void onHostPause() {
        Log.i(TAG, "onHostPause");
        if (mAdapter != null) {
            mAdapter.disableForegroundDispatch(this.reactContext.getCurrentActivity());
        }
    }

    @Override
    public void onHostDestroy() {
        Log.i(TAG, "onHostDestroy");
    }


    @ReactMethod
    public void addMRZView(final int tagId, final Promise promise) {
        try {
            mrzViews.add(new Integer(tagId));
            updateMRZView();
            promise.resolve(null);
        } catch (Exception ex) {
            promise.reject("ERR_CCCD_MRZ_VIEW", ex.toString());
        }
    }

    @ReactMethod
    public void removeMRZView(final int tagId, final Promise promise) {
        try {
            mrzViews.remove(new Integer(tagId));
            scanInfo.clean();
            promise.resolve(null);
        } catch (Exception ex) {
            promise.reject("ERR_CCCD_MRZ_VIEW", ex.toString());
        }
    }

    @ReactMethod
    public void startNFC(final Promise promise) {
        if (scanInfo.getMRZ() == null) {
            promise.reject(new Error("MRZ_SCAN_FIRST"));
            return;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (this.reactContext.checkSelfPermission(Manifest.permission.NFC) != PackageManager.PERMISSION_GRANTED) {
                promise.reject(new Error("NFC_NEED_GRANT_ACCESS"));
                return;
            }
        }

        mAdapter = NfcAdapter.getDefaultAdapter(this.reactContext);
        if (mAdapter == null) {
            // NFC is not available for device
            promise.reject(new Error("NFC_IS_NOT_AVAILABLE"));
            return;
        }
        if (!mAdapter.isEnabled()) {
            // NFC is available for device but not enabled
            promise.reject(new Error("NFC_IS_NOT_ENABLED"));
            return;
        }
        startReadingWithForegroundDispatch(this.reactContext.getCurrentActivity());
    }

    private void startReadingWithForegroundDispatch(Activity activity) {
        if (mAdapter == null) return;
        Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), NFC_SCAN_REQUEST_CODE, intent, PendingIntent.FLAG_MUTABLE);
        IntentFilter[] filters = new IntentFilter[]{
                new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
                new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
                new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED),
        };
        String[][] filter = new String[][]{new String[]{IsoDep.class.getName()}};
        mAdapter.enableForegroundDispatch(activity, pendingIntent, filters, filter);
    }

    @Override
    public void onRecognizedMRZ(MRZResult result, MRZInfo info) {
        scanInfo.setMRZ(result);
        WritableMap object = new WritableNativeMap();
        String genderString = "-";
        String birthdate = "-";
        String expiryDate = "-";
        String personalNumber2 = "-";
        String surnames = "-";
        String givenNames = "-";
        DateFormat inputFormatter = new SimpleDateFormat("yyMMdd");
        DateFormat ouputFormatter = new SimpleDateFormat("dd MMM yyyy");
        try {
            Date date = inputFormatter.parse(info.getDateOfBirth());
            birthdate = ouputFormatter.format(date);
        } catch (Exception ex) {

        }
        try {
            Date date = inputFormatter.parse(info.getDateOfExpiry());
            expiryDate = ouputFormatter.format(date);
        } catch (Exception ex) {

        }

        if (!info.getOptionalData2().contains("<")) {
            personalNumber2 = info.getOptionalData2();
        }
        String primaryIdentifier = info.getPrimaryIdentifier();
        if (primaryIdentifier != null) {
            String[] segments = primaryIdentifier.split("<<");
            if (segments.length == 2) {
                surnames = segments[0];
                givenNames = segments[1].replace("<", " ");
            }
        }
        switch (info.getGender()) {
            case MALE:
                genderString = "male";
                break;
            case FEMALE:
                genderString = "female";
                break;
            default:
                genderString = "unspecified";
                break;
        }

        object.putString("document_type", "id");
        object.putString("country_code", info.getNationality());
        object.putString("surnames", surnames);
        object.putString("given_names", givenNames);
        object.putString("document_number", info.getDocumentNumber());
        object.putString("nationality_country_code", info.getNationality());
        object.putString("birthdate", birthdate);
        object.putString("sex", genderString);
        object.putString("expiry_date", expiryDate);
        object.putString("personal_number", info.getPersonalNumber());
        object.putString("personal_number_2", personalNumber2);
        object.putString("primary_identifier", primaryIdentifier);
        sendEvent("ScanEvent", "MRZ_SCAN_FINISH", object);

    }
}
