package com.app.simplitend.locationupdates;

import static android.content.Intent.ACTION_BATTERY_CHANGED;
import static com.app.simplitend.apputils.NotificationService.CONTENT_TYPE_KEY;
import static com.app.simplitend.patientgeofencing.GeoFenceHelper.GEOFENCE_TAG;
import static com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver.LOCATION_EXTRA_KEY;
import static com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver.LOCATION_REQUEST_TAG;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.os.BatteryManager;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import com.app.simplitend.BuildConfig;
import com.app.simplitend.R;
import com.app.simplitend.apputils.AppUtil;
import com.app.simplitend.apputils.BottomNotificationActivity;
import com.app.simplitend.apputils.Constants;
import com.app.simplitend.apputils.RetrofitHelper;
import com.app.simplitend.caregiverdashboard.mvvm.NotificationApiService;
import com.app.simplitend.chats.SocketHelper;
import com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver;
import com.app.simplitend.welcome.welcomepatient.mvvm.models.CallResponse;
import com.google.android.gms.location.LocationServices;

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

import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class LocationService extends Service implements LocationClient.DefaultLocationUpdates {

    public static final String ACTION_START_LOCATION_UPDATES = "com.simplitent.action_start_lu";
    public static final String ACTION_STOP_LOCATION_UPDATES = "com.simplitent.action.stop_lu";
    public static final String LOCATION_NOTIFICATION_CHANNEL_ID = "location";
    public static final String FOOD_REMINDER_NOTIFICATION_CHANNEL_ID = "food_reminder_channel";

    public static final String LOCATION_UPDATE_MIN_INTERVAL = "min_interval";

    public static final int LOCATION_INTERVAL_BASE_TIME = 25 * 1000;

    private static final int LOCATION_UPDATES_NOTIFICATION_ID = 112;
    private static final int SENIOR_BATTERY_LOW_NOTIFICATION_ID = 2102;
    public static final String EXTRA_BATTERY_PERCENTAGE = "extra_battery_percentage";

    private DefaultLocationClient locationClient;

    private BroadcastReceiver batteryChangeReceiver;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(LOCATION_REQUEST_TAG, "onCreate: SERVICE STARTED");
        locationClient = new DefaultLocationClient(this,
                LocationServices.getFusedLocationProviderClient(this));

        SocketHelper.getInstance().establishConnection(null);

        batteryChangeReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (ACTION_BATTERY_CHANGED.equals(intent.getAction())){
                    int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
                    int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

                    float batteryPct = level * 100 / (float)scale;

                    if (batteryPct <= 25){

                        notifyPatientAboutBatteryLow(context, batteryPct);

                        notifyCaregiver(context, batteryPct);

                    }else{
                        AppUtil.setBatteryLowNotificationSent(context, false);
                        AppUtil.setBatteryLowNotificationShown(context, false);
                    }
                }
            }
        };

        try {
            registerReceiver(batteryChangeReceiver, new IntentFilter(ACTION_BATTERY_CHANGED));
        } catch (Exception e) {
            // do nothing
        }
    }

    private void notifyPatientAboutBatteryLow(Context context, float batteryPct) {
        if (AppUtil.isBatteryLowNotificationShown(context)) return;

        Notification notification = new Notification.Builder(context, LOCATION_NOTIFICATION_CHANNEL_ID)
                .setContentTitle(batteryPct + "% phone battery remaining")
                .setContentText("Connect to charger soon")
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .setOnlyAlertOnce(true)
                .setPriority(Notification.PRIORITY_HIGH)
                .build();

        NotificationManagerCompat.from(context).notify(SENIOR_BATTERY_LOW_NOTIFICATION_ID, notification);

        AppUtil.setBatteryLowNotificationShown(context, true);

        // showing bottom sheet
        Intent intent = new Intent(this, BottomNotificationActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(EXTRA_BATTERY_PERCENTAGE, (int) batteryPct);
        intent.putExtra(CONTENT_TYPE_KEY, Constants.BATTERY_LOW);
        startActivity(intent);
    }

    private void notifyCaregiver(Context context, float batteryPct) {
        if (AppUtil.isBatteryLowNotificationSent(context)) return;

        int[] chatsCred = AppUtil.getPatientGeofenceChatCred(context);

        Map<String, String> body = new HashMap<>();
        body.put("battery_perentage", batteryPct + "");
        body.put("caregiver_xid", chatsCred[1] + "");

        NotificationApiService apiService = RetrofitHelper.getRetrofit().create(NotificationApiService.class);
        apiService.notifyBatteryLow(body, "Bearer " + AppUtil.getPatientToken(context))
                .enqueue(new Callback<CallResponse<Object>>() {
                    @Override
                    public void onResponse(Call<CallResponse<Object>> call, Response<CallResponse<Object>> response) {
                        if (response.code() == 200){
                            AppUtil.setBatteryLowNotificationSent(context, true);
                        }else{
                            AppUtil.setBatteryLowNotificationSent(context, false);
                        }
                    }

                    @Override
                    public void onFailure(Call<CallResponse<Object>> call, Throwable t) {
                        AppUtil.setBatteryLowNotificationSent(context, false);
                    }
                });
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null && intent.getAction() != null) {
            switch (intent.getAction()) {
                case ACTION_START_LOCATION_UPDATES:
                    removeLocationUpdates();
                    startLocationUpdates(10_000); // 10 seconds interval
                    break;
                case ACTION_STOP_LOCATION_UPDATES:
                    stopLocationUpdates();

                    // setting that senior is at home for start
                    AppUtil.updateSeniorOutOfGeofence(this, false);
                    break;
            }
        }

        return START_REDELIVER_INTENT;
    }

    private void stopLocationUpdates() {
        stopForeground(true);
        stopSelf();
        removeLocationUpdates();
        SocketHelper.getInstance().closeConnection();
    }

    private void startLocationUpdates(int minInterval) {
        SocketHelper.getInstance().establishConnection(null);

        Notification notification = new NotificationCompat.Builder(this, LOCATION_NOTIFICATION_CHANNEL_ID)
                .setContentTitle("Your phone is connected to your caregiver")
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .setPriority(Notification.PRIORITY_HIGH)
                .setOngoing(true)
                .build();

        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        try {
            locationClient.getLocationUpdates(minInterval, this);
        } catch (LocationClient.LocationException e) {
            // do nothing
        }

        startForeground(LOCATION_UPDATES_NOTIFICATION_ID, notification);
        notificationManager.notify(LOCATION_UPDATES_NOTIFICATION_ID, notification);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(LOCATION_REQUEST_TAG, "onDestroy: SERVICE DESTROYED");
        // stop the location updates
        stopForeground(true);
        stopSelf();
        removeLocationUpdates();
        SocketHelper.getInstance().closeConnection();

        unregisterReceiver(batteryChangeReceiver);
    }

    public void removeLocationUpdates() {
        if (locationClient != null) {
            locationClient.removeLocationUpdates();
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void locationUpdates(Location location) {
        Intent intent = new Intent(this, PatientLocationUpdatesReceiver.class);
        intent.putExtra(LOCATION_EXTRA_KEY, location);
        sendBroadcast(intent);

        checkGeofence(location);
    }

    private void checkGeofence(Location currentLocation) {
        if (currentLocation != null) {
            String[] geofenceDetails = AppUtil.getPatientLatLng(this);
            if (geofenceDetails[0] != null && geofenceDetails[1] != null) {
                String geofenceRadius_str = AppUtil.getPatientGeofenceRadius(this);
                if (geofenceRadius_str != null) {
                    double geofenceRadius;

                    try {
                        geofenceRadius = Double.parseDouble(geofenceRadius_str);
                    } catch (Exception e) {
                        geofenceRadius = 0;
                    }

                    if (geofenceRadius > 0) {
                        Location homeLocation;
                        try {
                            homeLocation = new Location("homeLocation");
                            homeLocation.setLatitude(Double.parseDouble(geofenceDetails[0]));
                            homeLocation.setLongitude(Double.parseDouble(geofenceDetails[1]));
                        } catch (Exception e) {
                            homeLocation = null;
                        }

                        if (homeLocation != null) {
                            double distance = homeLocation.distanceTo(currentLocation);
                            if (distance > geofenceRadius) {
                                // senior is gone out of geofence
                                Log.d(GEOFENCE_TAG, "EXITED GEOFENCE");
                                boolean alreadyOutOfGeofence = AppUtil.isSeniorOutOfGeofence(this);
                                Log.d(GEOFENCE_TAG, "IS SENIOR ALREADY OUT OF GEOFENCE " + alreadyOutOfGeofence);

                                if (!alreadyOutOfGeofence) {
                                    distance = distance / 1609; // converting to miles

                                    notifyOutOfGeofence(this, String.format(Locale.getDefault(), "%.2f", distance));
                                    notifyPatient(this);

                                    // updating out of geofence
                                    AppUtil.updateSeniorOutOfGeofence(this, true);
                                }
                            } else {
                                // senior is at home
                                Log.d(GEOFENCE_TAG, "ENTERED GEOFENCE");
                                boolean alreadyOutOfGeofence = AppUtil.isSeniorOutOfGeofence(this);

                                if (alreadyOutOfGeofence) {

                                    // updating out of geofence
                                    AppUtil.updateSeniorOutOfGeofence(this, false);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void notifyPatient(Context context) {
        Log.d(GEOFENCE_TAG, "notifyPatient: SENDING MESSAGE TO SENIOR PHONE FROM CAREGIVER PHONE");

        String message = AppUtil.getPatientGeofenceMessage(context);
        if (message == null || message.trim().isEmpty()) {
            message = "You are too far from home";
        }

        int[] chatsCred = AppUtil.getPatientGeofenceChatCred(context);
        if (chatsCred[3] == -1 || chatsCred[4] == -1 || chatsCred[2] == -1) {
            Log.d(GEOFENCE_TAG, "notifyPatient: CANNOT SEND MESSAGE AS EITHER PAT_ID, CG_ID OR CHANNEL_ID IS NOT AVAILABLE");
            return;
        }

        Log.d(GEOFENCE_TAG, "notifyPatient: " + chatsCred[2]);
        // sending message
        sendMessage(message, chatsCred[3], chatsCred[4], chatsCred[2], chatsCred[3]);
    }

    private void sendMessage(@NonNull String message, int patientId, int cg_id, int channel_id, int patient_principal_id) {
        SocketHelper socketHelper = SocketHelper.getInstance();

        Log.d(GEOFENCE_TAG, "doInBackground: CONNECTING TO SOCKET");

        try {
            IO.Options options = new IO.Options();
            options.forceNew = true;
            options.reconnection = true;
            Socket mSocket = IO.socket(BuildConfig.SIMPLITEND_SOKCET_HOST, options);

            mSocket.on(channel_id + "", new Emitter.Listener() {
                @Override
                public void call(Object... args) {
                    try {
                        if (args.length >= 4) {
                            String received_sender_id = (String) args[1];
                            if (received_sender_id.equals(cg_id + "")) {
                                // message was sent successfully
                                Log.d(GEOFENCE_TAG, "GEOFENCE MESSAGE WAS SENT SUCCESSFULLY");

                                mSocket.disconnect();
                            }
                        }
                    } catch (Exception e) {
                        mSocket.disconnect();
                    }
                }
            });

            mSocket.on(Socket.EVENT_CONNECT, args -> {
                Log.d(GEOFENCE_TAG, "Socket connected ");
                socketHelper.sendMessage(message, cg_id + "", patientId + "", channel_id + "",
                        patient_principal_id < 0 ? null : patient_principal_id + "");
            });

            mSocket.on(Socket.EVENT_DISCONNECT, args -> {
                Log.d(GEOFENCE_TAG, "Socket disconnected ");
            });

            mSocket.on(Socket.EVENT_CONNECT_ERROR, args -> {
                Exception e = (Exception) args[0];
                Log.e(GEOFENCE_TAG, "call: ", e);
            });

            mSocket.connect();
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(GEOFENCE_TAG, "SocketHelper: ", e);
        }
    }

    private void notifyOutOfGeofence(Context context, String senior_address) {
        Log.d(GEOFENCE_TAG, "Sending notification to patient");
        Log.d(GEOFENCE_TAG, "Current location: " + senior_address);
        NotificationApiService apiService = RetrofitHelper.getRetrofit().create(NotificationApiService.class);

        Map<String, String> body = new HashMap<>();
        body.put("patient_id", AppUtil.getPatientUid(context) + "");
        body.put("address", senior_address);

        apiService.notifyOutOfGeoFence(body, "Bearer " + AppUtil.getPatientToken(context)).enqueue(new Callback<CallResponse<Object>>() {
            @Override
            public void onResponse(Call<CallResponse<Object>> call, Response<CallResponse<Object>> response) {
                if (response.code() == 200) {
                    Log.d(GEOFENCE_TAG, "OUT OF GEOFENCE NOTIFICATION SENT SUCCESSFULLY.");
                } else {
                    Log.d(GEOFENCE_TAG, "Couldn't notify patient " + response.message());
                }
            }

            @Override
            public void onFailure(Call<CallResponse<Object>> call, Throwable t) {
                Log.d(GEOFENCE_TAG, "Couldn't notify patient due to " + t);
            }
        });
    }
}
