package com.app.simplitend.patient_dashboard;

import static com.app.simplitend.callwhitelisting.CallService.CALL_UNBLOCKING_INTERVAL;
import static com.app.simplitend.callwhitelisting.CallUnBlockingWorker.CALL_BLOCKING_WORk;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.TelephonyManager;
import android.util.Log;

import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;

import com.app.simplitend.apputils.AppUtil;
import com.app.simplitend.callwhitelisting.CallUnBlockingWorker;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class CallReceiver extends BroadcastReceiver {

    private static final String TAG = "CallReceiver";

    private static int lastState = TelephonyManager.CALL_STATE_IDLE;
    private static Date callStartTime;
    private static boolean isIncoming;
    private static String savedNumber;  //because the passed incoming is only valid in ringing

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getExtras() == null) {
            Log.d(TAG, "onReceive: EXTRAS ARE NULL");
            return;
        }

//        logBundle(intent.getExtras());

        String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
        String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);

        int state = 0;
        if (TelephonyManager.EXTRA_STATE_IDLE.equals(stateStr)) {
            state = TelephonyManager.CALL_STATE_IDLE;
        } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(stateStr)) {
            state = TelephonyManager.CALL_STATE_OFFHOOK;
        } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(stateStr)) {
            state = TelephonyManager.CALL_STATE_RINGING;
        }

        onCallStateChanged(context, state, number);

    }

    protected void logBundle(Bundle bundle) {
        if (bundle != null) {
            for (String key : bundle.keySet()) {
                Log.e(TAG, key + " : " + (bundle.get(key) != null ? bundle.get(key) : "NULL"));
            }
        }
    }

    //Derived classes should override these to respond to specific events of interest
    protected void onIncomingCallStarted(Context ctx, String number, Date start) {
        Log.d(TAG, "onIncomingCallStarted: " + number);
        if (!AppUtil.isCallBlockingEnabled(ctx)) {
            // senior has deactivated the call blocking feature
            Log.d(TAG, "onIncomingCallStarted: CALL BLOCKING IS DEACTIVATED");
            return;
        }

        try {
            Phonenumber.PhoneNumber parsed_phone = PhoneNumberUtil.getInstance().parse(number, "US");
            number = "+" + parsed_phone.getCountryCode() + parsed_phone.getNationalNumber();
        } catch (Exception e) {
            // do nothing
        }

        Log.d(TAG, "AFTER FORMATTING PHONE NUMBER -> " + number);

        Set<String> white_contacts = AppUtil.getWhiteListedContacts(ctx);
        Log.d(TAG, "WHITE LISTED CONTACTS -> " + white_contacts);

        if (AppUtil.isUserSubscribed(ctx) && white_contacts != null && !white_contacts.contains(number)) {
            disconnectCall();
            Log.d(TAG, "CALL ENDED");
        }

    }

    public void disconnectCall(){
        try {
            String serviceManagerName = "android.os.ServiceManager";
            String serviceManagerNativeName = "android.os.ServiceManagerNative";
            String telephonyName = "com.android.internal.telephony.ITelephony";
            Class<?> telephonyClass;
            Class<?> telephonyStubClass;
            Class<?> serviceManagerClass;
            Class<?> serviceManagerNativeClass;
            Method telephonyEndCall;
            Object telephonyObject;
            Object serviceManagerObject;
            telephonyClass = Class.forName(telephonyName);
            telephonyStubClass = telephonyClass.getClasses()[0];
            serviceManagerClass = Class.forName(serviceManagerName);
            serviceManagerNativeClass = Class.forName(serviceManagerNativeName);
            Method getService = // getDefaults[29];
                    serviceManagerClass.getMethod("getService", String.class);
            Method tempInterfaceMethod = serviceManagerNativeClass.getMethod("asInterface", IBinder.class);
            Binder tmpBinder = new Binder();
            tmpBinder.attachInterface(null, "fake");
            serviceManagerObject = tempInterfaceMethod.invoke(null, tmpBinder);
            IBinder retbinder = (IBinder) getService.invoke(serviceManagerObject, "phone");
            Method serviceMethod = telephonyStubClass.getMethod("asInterface", IBinder.class);
            telephonyObject = serviceMethod.invoke(null, retbinder);
            telephonyEndCall = telephonyClass.getMethod("endCall");
            telephonyEndCall.invoke(telephonyObject);

        } catch (Exception e) {
            Log.e(TAG,
                    "FATAL ERROR: could not connect to telephony subsystem");
            Log.e(TAG, "Exception object: " + e);
        }
    }

    protected void onOutgoingCallStarted(Context ctx, String number, Date start) {
        Log.d(TAG, "onOutgoingCallStarted: " + number);
        if ("911".equals(number)){
            Log.d(TAG, "CALLED 911");
            WorkManager.getInstance(ctx).cancelAllWorkByTag(TAG);
            WorkRequest workRequest = new OneTimeWorkRequest.Builder(CallUnBlockingWorker.class)
                    .setInitialDelay(CALL_UNBLOCKING_INTERVAL, TimeUnit.MINUTES)
                    .addTag(CALL_BLOCKING_WORk)
                    .build();

            WorkManager.getInstance(ctx).enqueue(workRequest);

            AppUtil.setIsCallBlockingEnabled(ctx, false);
            Log.d(TAG, "CALL BLOCKING DISABLED");
        }
    }

    protected void onIncomingCallEnded(Context ctx, String number, Date start, Date end) {
    }

    protected void onOutgoingCallEnded(Context ctx, String number, Date start, Date end) {
    }

    protected void onMissedCall(Context ctx, String number, Date start) {
    }

    //Deals with actual events

    //Incoming call-  goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
    //Outgoing call-  goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
    public void onCallStateChanged(Context context, int state, String number) {
        if (lastState == state) {
            //No change, debounce extras
            return;
        }
        switch (state) {
            case TelephonyManager.CALL_STATE_RINGING:
                isIncoming = true;
                callStartTime = new Date();
                savedNumber = number;
                onIncomingCallStarted(context, number, callStartTime);
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                //Transition of ringing->offhook are pickups of incoming calls.  Nothing done on them
                if (lastState != TelephonyManager.CALL_STATE_RINGING) {
                    isIncoming = false;
                    callStartTime = new Date();
                    onOutgoingCallStarted(context, number, callStartTime);
                }
                break;
            case TelephonyManager.CALL_STATE_IDLE:
                //Went to idle-  this is the end of a call.  What type depends on previous state(s)
                if (lastState == TelephonyManager.CALL_STATE_RINGING) {
                    //Ring but no pickup-  a miss
                    onMissedCall(context, savedNumber, callStartTime);
                } else if (isIncoming) {
                    onIncomingCallEnded(context, savedNumber, callStartTime, new Date());
                } else {
                    onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());
                }
                break;
        }
        lastState = state;
    }
}