/*
 * Copyright 2025 Circle Internet Group, Inc. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.cybavo.reactnative.wallet.service;

import com.cybavo.wallet.service.auth.Auth;
import com.cybavo.wallet.service.auth.BackupChallenge;
import com.cybavo.wallet.service.auth.PinSecret;
import com.cybavo.wallet.service.auth.SignInState;
import com.cybavo.wallet.service.auth.SignInStateListener;
import com.cybavo.wallet.service.auth.results.ChangePinCodeResult;
import com.cybavo.wallet.service.auth.results.ForgotPinCodeResult;
import com.cybavo.wallet.service.auth.results.GetRestoreQuestionsResult;
import com.cybavo.wallet.service.auth.results.GetUserStateResult;
import com.cybavo.wallet.service.auth.results.RecoverPinCodeResult;
import com.cybavo.wallet.service.auth.results.RegisterPhoneNumberResult;
import com.cybavo.wallet.service.auth.results.RegisterReferralCodeResult;
import com.cybavo.wallet.service.auth.results.RestorePinCodeResult;
import com.cybavo.wallet.service.auth.results.RevokeUserResult;
import com.cybavo.wallet.service.auth.results.SetPushDeviceTokenResult;
import com.cybavo.wallet.service.auth.results.SetupBackupChallengeResult;
import com.cybavo.wallet.service.auth.results.SetupPinCodeResult;
import com.cybavo.wallet.service.auth.results.SignInResult;
import com.cybavo.wallet.service.auth.results.SignUpResult;
import com.cybavo.wallet.service.auth.results.VerifyOtpResult;
import com.cybavo.wallet.service.auth.results.VerifyRecoveryCodeResult;
import com.cybavo.wallet.service.auth.results.VerifyRestoreQuestionsResult;
import com.cybavo.wallet.service.auth.results.CreateKycResult;
import com.cybavo.wallet.service.auth.results.GetKycAccessTokenResult;
import com.cybavo.wallet.service.auth.results.GetKycShareTokenResult;
import com.cybavo.wallet.service.auth.results.CheckKycSettingResult;
import com.cybavo.wallet.service.auth.results.GetApplicantStatusResult;
import com.cybavo.wallet.service.auth.results.SearchUserResult;
import com.cybavo.wallet.service.auth.results.UpdateRealNameResult;
import com.cybavo.wallet.service.auth.results.ValidatePinCodeResult;
import com.cybavo.wallet.service.wallet.results.RequestSecureTokenResult;
import com.facebook.react.bridge.Arguments;
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.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReadableMapKeySetIterator;

import java.util.HashMap;
import java.util.Map;

public class CybavoAuthModule extends ReactContextBaseJavaModule implements SignInStateListener {

    private final ReactApplicationContext mReactContext;

    private Auth mAuth;
    private Auth getAuth() {
        if (mAuth == null) {
            mAuth = Auth.getInstance();
            mAuth.addSignInStateListener(this);
        }
        return mAuth;
    }

    @Override
    public void onSignInStateChanged(final SignInState signInState) {
        mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit("onSignInStateChanged", signInState.ordinal());
    }

    public CybavoAuthModule(final ReactApplicationContext reactContext) {
        super(reactContext);
        this.mReactContext = reactContext;
    }

    @Override
    public String getName() {
        return "Auth";
    }

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();

        // SignInState
        final WritableMap signInState = Arguments.createMap();
        for (SignInState state : SignInState.values()) {
            signInState.putInt(state.name(), state.ordinal());
        }
        constants.put("SignInState", signInState);

        // Events
        final WritableMap events = Arguments.createMap();
        events.putString("onSignInStateChanged", "onSignInStateChanged");
        constants.put("Events", events);

        return constants;
    }

    @ReactMethod
    public void getSignInState(final Promise promise) {
        promise.resolve(getAuth().getSignInState().ordinal());
    }

    @ReactMethod
    public void signIn(final String token, final String identityProvider, final ReadableMap extraAttributes, final Promise promise) {
    final Map<String, String> extras = new HashMap<>();
                if (extraAttributes != null) {
                    final ReadableMapKeySetIterator itor = extraAttributes.keySetIterator();
                    while (itor.hasNextKey()) {
                        final String key = itor.nextKey();
                        extras.put(key, extraAttributes.getString(key));
                    }
            }
        getAuth().signIn(token, identityProvider, extras, new PromiseCallback<SignInResult>(promise));
    }

    @ReactMethod
    public void signUp(final String token, final String identityProvider, final ReadableMap extraAttributes, final Promise promise) {
        final Map<String, String> extras = new HashMap<>();
            if (extraAttributes != null) {
                final ReadableMapKeySetIterator itor = extraAttributes.keySetIterator();
                while (itor.hasNextKey()) {
                    final String key = itor.nextKey();
                    extras.put(key, extraAttributes.getString(key));
                }
        }
        getAuth().signUp(token, identityProvider, extras, new PromiseCallback<SignUpResult>(promise));
    }

    @ReactMethod
    public void signOut(final Promise promise) {
        getAuth().signOut();
        promise.resolve(null);
    }

    @ReactMethod
    public void getUserState(final Promise promise) {
        getAuth().getUserState(new PromiseCallback<GetUserStateResult>(promise));
    }

    @ReactMethod
    public void setupPinCodeWithPinSecret(final ReadableMap pinSecretMap, final Promise promise) {
        // Get PinSecret
        PinSecret pinSecret = PinSecretBridge.fromReadableMap(pinSecretMap);
        if (pinSecret == null) {
            promise.reject(new NullPointerException("PinSecret not found"));
            return;
        }

        getAuth().setupPinCode(pinSecret, new PromiseCallback<SetupPinCodeResult>(promise));
    }

    @ReactMethod
    public void setupBackupChallengeWithPinSecret(final ReadableMap pinSecretMap,
                                     final ReadableMap challenge1, final ReadableMap challenge2, final ReadableMap challenge3,
                                     final Promise promise) {
        // Get PinSecret
        PinSecret pinSecret = PinSecretBridge.fromReadableMap(pinSecretMap);
        if (pinSecret == null) {
            promise.reject(new NullPointerException("PinSecret not found"));
            return;
        }

        getAuth().setupBackupChallenge(pinSecret,
                makeBackupChallenge(challenge1), makeBackupChallenge(challenge2), makeBackupChallenge(challenge3),
                new PromiseCallback<SetupBackupChallengeResult>(promise));
    }

    @ReactMethod
    public void changePinCodeWithPinSecret(final ReadableMap newPinSecretMap, final ReadableMap currentPinSecretMap, final Promise promise) {
        // Get PinSecret
        PinSecret newPinSecret = PinSecretBridge.fromReadableMap(newPinSecretMap);
        if (newPinSecret == null) {
            promise.reject(new NullPointerException("New PinSecret not found"));
            return;
        }

        // Get PinSecret
        PinSecret currentPinSecret = PinSecretBridge.fromReadableMap(currentPinSecretMap);
        if (currentPinSecret == null) {
            promise.reject(new NullPointerException("Current PinSecret not found"));
            return;
        }


        getAuth().changePinCode(newPinSecret, currentPinSecret, new PromiseCallback<ChangePinCodeResult>(promise));
    }

    @ReactMethod
    public void restorePinCodeWithPinSecret(final ReadableMap newPinSecretMap,
                               final ReadableMap challenge1, final ReadableMap challenge2, final ReadableMap challenge3,
                               final Promise promise) {
        // Get PinSecret
        PinSecret newPinSecret = PinSecretBridge.fromReadableMap(newPinSecretMap);
        if (newPinSecret == null) {
            promise.reject(new NullPointerException("PinSecret not found"));
            return;
        }

        getAuth().restorePinCode(newPinSecret,
                makeBackupChallenge(challenge1), makeBackupChallenge(challenge2), makeBackupChallenge(challenge3),
                new PromiseCallback<RestorePinCodeResult>(promise));
    }

    @ReactMethod
    public void forgotPinCode(final Promise promise) {
        getAuth().forgotPinCode(new PromiseCallback<ForgotPinCodeResult>(promise));
    }

    @ReactMethod
    public void recoverPinCodeWithPinSecret(final ReadableMap newPinSecretMap, final String recoveryCode,
                               final Promise promise) {
        // Get PinSecret
        PinSecret newPinSecret = PinSecretBridge.fromReadableMap(newPinSecretMap);
        if (newPinSecret == null) {
            promise.reject(new NullPointerException("PinSecret not found"));
            return;
        }

        getAuth().recoverPinCode(newPinSecret, recoveryCode,
                new PromiseCallback<RecoverPinCodeResult>(promise));
    }

    @ReactMethod
    public void verifyRecoveryCode(final String recoveryCode, final Promise promise) {
        getAuth().verifyRecoveryCode(recoveryCode, new PromiseCallback<VerifyRecoveryCodeResult>(promise));
    }

    @ReactMethod
    public void getRestoreQuestions(final Promise promise) {
        getAuth().getRestoreQuestions(new PromiseCallback<GetRestoreQuestionsResult>(promise));
    }

    @ReactMethod
    public void verifyRestoreQuestions(final ReadableMap challenge1, final ReadableMap challenge2, final ReadableMap challenge3,
                               final Promise promise) {
        getAuth().verifyRestoreQuestions(makeBackupChallenge(challenge1), makeBackupChallenge(challenge2), makeBackupChallenge(challenge3),
                new PromiseCallback<VerifyRestoreQuestionsResult>(promise));
    }

    @ReactMethod
    public void setPushDeviceToken(final String pushDeviceToken, final Promise promise) {
        getAuth().setPushDeviceToken(pushDeviceToken, new PromiseCallback<SetPushDeviceTokenResult>(promise));
    }

    private static BackupChallenge makeBackupChallenge(final ReadableMap map) {
        final String question = map.getString("question");
        final String answer = map.getString("answer");
        return BackupChallenge.make(question != null ? question : "", answer != null ? answer : "");
    }

    @ReactMethod
    public void registerPhoneNumber(final String countryCode, final String phone, final double duration, final Promise promise) {
        getAuth().registerPhoneNumber(countryCode, phone, (long)duration, new PromiseCallback<RegisterPhoneNumberResult>(promise));
    }

    @ReactMethod
    public void verifyOtp(final String actionToken, final String code, final Promise promise) {
        getAuth().verifyOtp(actionToken, code, new PromiseCallback<VerifyOtpResult>(promise));
    }

    @ReactMethod
    public void createKyc(final String country, final Promise promise) {
        getAuth().createKyc(country, new PromiseCallback<CreateKycResult>(promise){
            @Override
            public void onResult(CreateKycResult result) {
                WritableMap map = BridgeHelper.objectToMap(result);
                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void getKycAccessToken(final Promise promise) {
        getAuth().getKycAccessToken(new PromiseCallback<GetKycAccessTokenResult>(promise){
            @Override
            public void onResult(GetKycAccessTokenResult result) {
                WritableMap map = BridgeHelper.objectToMap(result);
                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void getKycShareToken(final Promise promise) {
        getAuth().getKycShareToken(new PromiseCallback<GetKycShareTokenResult>(promise){
            @Override
            public void onResult(GetKycShareTokenResult result) {
                WritableMap map = BridgeHelper.objectToMap(result);
                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void checkKycSetting(final Promise promise) {
        getAuth().checkKycSetting(new PromiseCallback<CheckKycSettingResult>(promise){
            @Override
            public void onResult(CheckKycSettingResult result) {
                WritableMap map = BridgeHelper.objectToMap(result);
                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void getApplicantStatus(final Promise promise) {
        getAuth().getApplicantStatus(new PromiseCallback<GetApplicantStatusResult>(promise){
            @Override
            public void onResult(GetApplicantStatusResult result) {
                WritableMap map = BridgeHelper.objectToMap(result);
                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void searchUser(final String keyword, final Promise promise) {
        getAuth().searchUser(keyword, new PromiseCallback<SearchUserResult>(promise){
            @Override
            public void onResult(SearchUserResult result) {
                WritableMap map = BridgeHelper.objectToMap(result);
                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void updateRealName(final String realName, final Promise promise) {
        getAuth().updateRealName(realName, new PromiseCallback<UpdateRealNameResult>(promise){
            @Override
            public void onResult(UpdateRealNameResult result) {
                WritableMap map = BridgeHelper.objectToMap(result);
                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void revokeUserWithNoPin(final Promise promise) {
        getAuth().revokeUser(new PromiseCallback<RevokeUserResult>(promise));
    }

    @ReactMethod
    public void revokeUserWithPinSecret(final ReadableMap pinSecretMap, final Promise promise) {

        // Get PinSecret
        PinSecret pinSecret = PinSecretBridge.fromReadableMap(pinSecretMap);
        if (pinSecret == null) {
            promise.reject(new NullPointerException("PinSecret not found"));
            return;
        }

        getAuth().revokeUser(pinSecret, new PromiseCallback<RevokeUserResult>(promise));
    }

    @ReactMethod
    public void validatePinCodeWithPinSecret(final ReadableMap pinSecretMap, final Promise promise) {

        // Get PinSecret
        PinSecret pinSecret = PinSecretBridge.fromReadableMap(pinSecretMap);
        if (pinSecret == null) {
            promise.reject(new NullPointerException("PinSecret not found"));
            return;
        }

        getAuth().validatePinCode(pinSecret, new PromiseCallback<ValidatePinCodeResult>(promise));
    }

    @ReactMethod
    public void registerReferralCode(final String code, final Promise promise) {
        getAuth().registerReferralCode(code, new PromiseCallback<RegisterReferralCodeResult>(promise));
    }

    /** Deprecated */
    @ReactMethod
    public void setupPinCode(final String pinCode, final Promise promise) {
        getAuth().setupPinCode(pinCode, new PromiseCallback<SetupPinCodeResult>(promise));
    }
    /** Deprecated */
    @ReactMethod
    public void setupBackupChallenge(final String pinCode,
                                     final ReadableMap challenge1, final ReadableMap challenge2, final ReadableMap challenge3,
                                     final Promise promise) {
        getAuth().setupBackupChallenge(pinCode,
                makeBackupChallenge(challenge1), makeBackupChallenge(challenge2), makeBackupChallenge(challenge3),
                new PromiseCallback<SetupBackupChallengeResult>(promise));
    }
    /** Deprecated */
    @ReactMethod
    public void changePinCode(final String newPinCode, final String currentPinCode, final Promise promise) {
        getAuth().changePinCode(newPinCode, currentPinCode, new PromiseCallback<ChangePinCodeResult>(promise));
    }
    /** Deprecatdd */
    @ReactMethod
    public void restorePinCode(final String newPinCode,
                               final ReadableMap challenge1, final ReadableMap challenge2, final ReadableMap challenge3,
                               final Promise promise) {
        getAuth().restorePinCode(newPinCode,
                makeBackupChallenge(challenge1), makeBackupChallenge(challenge2), makeBackupChallenge(challenge3),
                new PromiseCallback<RestorePinCodeResult>(promise));
    }
    /** Deprecated */
    @ReactMethod
    public void recoverPinCode(final String newPinCode, final String recoveryCode,
                               final Promise promise) {
        getAuth().recoverPinCode(newPinCode, recoveryCode,
                new PromiseCallback<RecoverPinCodeResult>(promise));
    }
    /** Deprecated */
    @ReactMethod
    public void revokeUser(final String pinCode, final Promise promise) {
        getAuth().revokeUser(pinCode, new PromiseCallback<RevokeUserResult>(promise));
    }
    /** Deprecated */
    @ReactMethod
    public void validatePinCode(final String currentPinCode, final Promise promise) {
        getAuth().validatePinCode(currentPinCode, new PromiseCallback<ValidatePinCodeResult>(promise));
    }
}
