package com.app.simplitend.patient_dashboard;

import static android.content.Context.ACTIVITY_SERVICE;
import static com.app.simplitend.locationupdates.LocationService.LOCATION_INTERVAL_BASE_TIME;
import static com.app.simplitend.locationupdates.LocationService.LOCATION_UPDATE_MIN_INTERVAL;
import static com.app.simplitend.patientgeofencing.GeoFenceHelper.GEOFENCE_ID;
import static com.app.simplitend.patientgeofencing.GeoFenceHelper.GEOFENCE_TAG;
import static com.app.simplitend.patientgeofencing.PatientLocationUpdatesReceiver.LOCATION_REQUEST_TAG;

import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModel;

import com.app.simplitend.apputils.AppUtil;
import com.app.simplitend.apputils.RetrofitHelper;
import com.app.simplitend.caregiverdashboard.mvvm.CaregiverMainViewModel;
import com.app.simplitend.caregiverdashboard.mvvm.CgHomeContracts;
import com.app.simplitend.caregiverdashboard.mvvm.CgHomeRepository;
import com.app.simplitend.caregiverdashboard.mvvm.NotificationApiService;
import com.app.simplitend.caregiverdashboard.mvvm.models.GeoFenceDetails;
import com.app.simplitend.locationupdates.LocationService;
import com.app.simplitend.patientgeofencing.GeoFenceHelper;
import com.app.simplitend.patientprofile.PatientProfileAPIService;
import com.app.simplitend.patientprofile.medreminder.mvvm.models.NearestActivity;
import com.app.simplitend.patientprofile.medreminder.mvvm.models.NearestReminder;
import com.app.simplitend.patientprofile.medreminder.mvvm.models.ReminderResult;
import com.app.simplitend.patientprofile.setuproutine.mvvm.RoutineDetails;
import com.app.simplitend.welcome.welcomepatient.mvvm.models.CallResponse;
import com.app.simplitend.welcome.welcomepatient.mvvm.models.PatientData;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingClient;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.model.LatLng;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class PatientMainViewModel extends ViewModel {

    private static final String TAG = "PatientMainViewModel";

    private final CgHomeRepository cgHomeRepository;

    private final NotificationApiService notificationApiService;

    public String ongoingActivityText, upcomingActivityText;
    public String upcomingReminderText, dailyReminderText;

    public List<ReminderResult> remindersList;
    public List<RoutineDetails> activityList;

    public PatientMainViewModel() {
        cgHomeRepository = CgHomeRepository.getHomeRepository();
        notificationApiService = RetrofitHelper.getRetrofit().create(NotificationApiService.class);

        this.ongoingActivityText = this.upcomingActivityText =
                this.upcomingReminderText = this.dailyReminderText = "Loading...";
    }

    public void setGeofence(Activity activity, GeoFenceDetails geoFenceDetails, PatientData patientData) {
        LatLng latLng;
        float radius;
        try {
            latLng = new LatLng(Double.parseDouble(patientData.lat), Double.parseDouble(patientData.lng));
            radius = Float.parseFloat(geoFenceDetails.radius);
        } catch (Exception e) {
            latLng = null;
            radius = 0;
        }

        if (latLng != null && radius != 0) {
            if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                Log.d(GEOFENCE_TAG, "setGeofence: NO FINE LOCATION PERMISSION" );
                return;
            }

            addGeoFence(latLng, radius, activity, geoFenceDetails.type, geoFenceDetails.message);
        }
    }

    @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
    public void addLocationUpdates(Activity activity) {
        if (!isLocationServiceRunning(activity)) {
            Log.d(LOCATION_REQUEST_TAG, "REQUESTING LOCATION UPDATES");
            Toast.makeText(activity, "Location updates started", Toast.LENGTH_SHORT).show();
            Intent intent = new Intent(activity, LocationService.class);
            intent.setAction(LocationService.ACTION_START_LOCATION_UPDATES);
            intent.putExtra(LOCATION_UPDATE_MIN_INTERVAL, LOCATION_INTERVAL_BASE_TIME);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                activity.startForegroundService(intent);
            }else {
                activity.startService(intent);
            }
        }
    }

    public boolean isLocationServiceRunning(Activity activity){
        ActivityManager activityManager = (ActivityManager) activity.getSystemService(ACTIVITY_SERVICE);
        for(ActivityManager.RunningServiceInfo service: activityManager.getRunningServices(Integer.MAX_VALUE)) {
            if(LocationService.class.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
        return false;
    }

    public void getGeoFenceDetails(String p_id,
                                   String cg_xid,
                                   @NonNull String token,
                                   @NonNull CgHomeContracts.GetGeoFenceCallback getGeoFenceCallback){
        cgHomeRepository.getGeoFenceDetails(p_id, cg_xid, token, getGeoFenceCallback);
    }

    @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
    private void addGeoFence(@NonNull LatLng latLng, float GEOFENCING_RADIUS , Activity activity, String unit, String message) {
        AppUtil.removeGeofence(activity);

        GeoFenceHelper geoFenceHelper = new GeoFenceHelper(activity);
        GeofencingClient geofencingClient = LocationServices.getGeofencingClient(activity);

        // checking for background location updates for API level 29 and above
        if (Build.VERSION.SDK_INT >= 29) {
            if (ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                Log.d(GEOFENCE_TAG, "setGeofence: NO BACKGROUND PERMISSION" );
                return;
            }
        }

        Geofence geofence = geoFenceHelper.getGeoFence(GEOFENCE_ID, latLng, GEOFENCING_RADIUS,
                Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT);
        GeofencingRequest geofencingRequest = geoFenceHelper.getGeoFencingRequest(geofence);
        PendingIntent pendingIntent = geoFenceHelper.getPendingIntent();

        geofencingClient.addGeofences(geofencingRequest, pendingIntent)
                .addOnSuccessListener(aVoid -> {
                    Log.d(GEOFENCE_TAG, "Geofence added successfully. " + latLng + " Radius: " + GEOFENCING_RADIUS + " meters");
                    AppUtil.updatePatientGeofence(activity, latLng.latitude+"", latLng.longitude+"",
                            GEOFENCING_RADIUS+"", unit, message);
                })
                .addOnFailureListener(e -> {
                    Log.d(GEOFENCE_TAG, "onFailure: Geofence couldn't be added: " + e.getLocalizedMessage()  + " " + latLng + " Radius: " + GEOFENCING_RADIUS);
                });

    }

    public void notifyRequestedSOS(String patient_Id, String token){
        Map<String, String> body = new HashMap<>();
        body.put("patient_id", patient_Id);

        notificationApiService.notifyRequestedSOS(body, "Bearer " + token)
                .enqueue(new Callback<CallResponse<Object>>() {
                    @Override
                    public void onResponse(Call<CallResponse<Object>> call, Response<CallResponse<Object>> response) {
                        Log.d(TAG, "onResponse: NOTIFIED CG ABOUT SOS REQUESTED");
                    }

                    @Override
                    public void onFailure(Call<CallResponse<Object>> call, Throwable t) {
                        Log.d(TAG, "onFailure: Notifying cg about SoS failed" + t);
                    }
                });
    }

    public void notifyRequestedDirections(String patient_Id, String address, String token){
        Map<String, String> body = new HashMap<>();
        body.put("patient_id", patient_Id);

        notificationApiService.notifyRequestedDirections(body, address, "Bearer " + token)
                .enqueue(new Callback<CallResponse<Object>>() {
                    @Override
                    public void onResponse(Call<CallResponse<Object>> call, Response<CallResponse<Object>> response) {
                        Log.d(TAG, "onResponse: NOTIFIED CG ABOUT DIRECTIONS REQUESTED");
                    }

                    @Override
                    public void onFailure(Call<CallResponse<Object>> call, Throwable t) {
                        Log.d(TAG, "onFailure: Notifying cg about DIRECTIONS failed" + t);
                    }
                });
    }

    public synchronized void getNearestActivity(List<RoutineDetails> activityList,
                                                @NonNull CaregiverMainViewModel.GetNearestActivityCallback activityCallback){
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Handler handler = new Handler(Looper.getMainLooper());

        executor.execute(() -> {
            // Background work

            NearestActivity nearestActivity = new NearestActivity();

            try {

                for (RoutineDetails activity: activityList){
                    SimpleDateFormat input_sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());

                    Date start_date = input_sdf.parse(activity.routine_start_time);
                    Date end_date = input_sdf.parse(activity.routine_end_time);

                    if (start_date == null || end_date == null) throw new Exception();

                    Calendar cur_cal = Calendar.getInstance();

                    // matching date and seconds of the current date and routine dates
                    Date cur_date = new Date(start_date.getYear(), start_date.getMonth(), start_date.getDate(), cur_cal.get(Calendar.HOUR_OF_DAY), cur_cal.get(Calendar.MINUTE), 0);
                    start_date.setSeconds(0);
                    end_date.setSeconds(0);

                    // checking on going activity
                    if (cur_date.getTime() >= start_date.getTime() &&
                            cur_date.getTime() <= end_date.getTime()){
                        // on going activity
                        nearestActivity.ongoing_activity_name = activity.routine_title;
                        nearestActivity.ongoing_start_time = activity.routine_start_time;
                        nearestActivity.ongoing_end_time = activity.routine_end_time;
                    }

                    // checking upcoming activity
                    if (start_date.getTime() > cur_date.getTime() && smallestTime(activity.routine_start_time, nearestActivity.upcoming_time)){
                        // upcoming_time for this reminder is less than nearestReminder.upcoming_time
                        // thus, updating nearest reminder

                        nearestActivity.upcoming_time = activity.routine_start_time;
                        nearestActivity.activity_id = activity.id;
                        nearestActivity.upcoming_activity_name = activity.routine_title;
                    }

                }

            }catch (Exception e){
                // do nothing
            }

            handler.post(() -> activityCallback.nearestActivity(nearestActivity));
        });
    }

    // check if the time has passed current time or not
    private boolean alreadyPassed(String time1) {
        SimpleDateFormat input_sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());

        try {
            Date date = input_sdf.parse(time1);
            if (date == null) throw new Exception();

            Calendar cur_cal = Calendar.getInstance();
            Date cur_date = new Date(date.getYear(), date.getMonth(), date.getDate(), cur_cal.get(Calendar.HOUR_OF_DAY), cur_cal.get(Calendar.MINUTE), date.getSeconds());

            return date.getTime() <= cur_date.getTime();
        } catch (Exception e) {
            return false;
        }
    }

    // returns true if time1 is less than time2;
    private boolean smallestTime(@NonNull String time1, String time2) {
        if (time2 == null) return true;

        // comparing this 2 times with current time
        // first converting time string to calendar
        SimpleDateFormat input_sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());

        try {
            Date date1 = input_sdf.parse(time1);
            Date date2 = input_sdf.parse(time2);

            if (date2 == null || date1 == null) throw new Exception();

            // All times above dates are of same date 01 Jan 1970
            // but, of different times i.e. hh:mm:ss

            long t1 = date1.getTime();
            long t2 = date2.getTime();

            return t1 < t2;
        }catch (Exception e){
            // if something goes wrong, returning the time1
            return true;
        }
    }

    public void updatePatientCurrentLocation(@NonNull LatLng latLng,
                                             @NonNull String patient_id,
                                             @NonNull String token){
        PatientProfileAPIService apiService = RetrofitHelper.getRetrofit().create(PatientProfileAPIService.class);

        Map<String, String> body = new HashMap<>();
        body.put("patient_id", patient_id);
        body.put("lat", latLng.latitude + "");
        body.put("lng", latLng.longitude + "");

        apiService.updatePatientCurrLocation(body, token)
                .enqueue(new Callback<CallResponse<Object>>() {
                    @Override
                    public void onResponse(Call<CallResponse<Object>> call, Response<CallResponse<Object>> response) {
                        Log.d(TAG, "PATIENT CURRENT LOCATION UPDATED");
                    }

                    @Override
                    public void onFailure(Call<CallResponse<Object>> call, Throwable t) {
                        Log.e(TAG, "onFailure: PATIENT CURRENT LOCATION UPDATE", t);
                    }
                });

    }

    public synchronized void getNearestReminder(List<ReminderResult> reminderResultList,
                                                @NonNull CaregiverMainViewModel.GetNearestResultCallback nearestResultCallback){

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Handler handler = new Handler(Looper.getMainLooper());

        executor.execute(() -> {
            // background thread work

            NearestReminder nearestReminder = new NearestReminder();

            for (ReminderResult reminder: reminderResultList){
                if (!alreadyPassed(reminder.medicine_time)){
                    nearestReminder.upcoming_time = reminder.medicine_time;
                    nearestReminder.reminder_id = reminder.id;
                    nearestReminder.medication_name = reminder.medicine_name;
                    break;
                }
            }

            // callback through main thread
            handler.post(() -> nearestResultCallback.nearestReminder(nearestReminder));
        });
    }
}
