package com.app.simplitend.welcome.welcomepatient.fragments.register;

import static android.content.Context.LOCATION_SERVICE;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;

import com.app.simplitend.apputils.EditTextErrorRemover;
import com.app.simplitend.apputils.TextUtils;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.widget.Autocomplete;
import com.google.android.libraries.places.widget.model.AutocompleteActivityMode;
import com.skydoves.powerspinner.OnSpinnerItemSelectedListener;
import com.app.simplitend.R;
import com.app.simplitend.apputils.AppUtil;
import com.app.simplitend.databinding.LocationFragmentBinding;
import com.app.simplitend.welcome.welcomepatient.mvvm.WelcomeViewModel;
import com.app.simplitend.welcome.welcomepatient.mvvm.models.PatientData;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

public class LocationFragment extends Fragment implements OnMapReadyCallback,
        GoogleMap.OnMapClickListener, LocationListener {
    private static final String TAG = "LocationFragment";

    private static final String UNITED_STATES = "United States";

    // view binding
    protected LocationFragmentBinding binding;

    // country list
    private ArrayList<String> countryList;

    // Map for country and their states
    private HashMap<String, ArrayList<String>> country_N_states_map;

    // user location selection marker
    private Marker marker = null;

    // Google map object
    private GoogleMap googleMap;

    // user selected current location
    private LatLng currentLocation;

    // Fused location object
    private FusedLocationProviderClient fusedLocationProviderClient;
    private LocationRequest locationRequest;

    private ActivityResultLauncher<Intent> startAutocompleteMapSearch;

    // viewmodel
    private WelcomeViewModel viewModel;

    public LocationFragment() {
        // required empty const.
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = LocationFragmentBinding.inflate(inflater, container, false);

        try {
            // setting height of mapview w.r.t screen size
            int map_height = (int) (Resources.getSystem().getDisplayMetrics().heightPixels * 0.7f);
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) binding.mapView.getLayoutParams();
            layoutParams.height = map_height;
            binding.mapView.setLayoutParams(layoutParams);
        } catch (Exception e) {
            // do nothing as default height is 350sdp
        }

        viewModel = new ViewModelProvider(requireActivity()).get(WelcomeViewModel.class);

        initViews();

        clickEvents();

        return binding.getRoot();
    }

    @Override
    public void onStop() {
        super.onStop();
        if (getActivity() != null) {
            getActivity().getWindow()
                    .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG, "location updates removed");
        fusedLocationProviderClient.removeLocationUpdates(this);
    }

    @SuppressLint("ClickableViewAccessibility")
    private void initViews() {

        loadCountriesAndStates();

        inputFieldFocusManage();

        try {
            loadPatientAddressSavedState();
        } catch (Exception e) {
            Log.e(TAG, "initViews: ", e);
        }

        // requesting scroll view to stop listening to gestures for mapview
        binding.transImg.setOnTouchListener((v, event) -> {
            binding.scrollView.requestDisallowInterceptTouchEvent(true);
            return false;
        });

        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(requireContext());
        locationRequest = new LocationRequest.Builder(LocationRequest.PRIORITY_HIGH_ACCURACY, 5000)
                .build();

        registerMapSearchResultLauncher();

        getLocationPermission();

        // initializing maps
        GoogleMapOptions gmo = new GoogleMapOptions();
        gmo.mapType(GoogleMap.MAP_TYPE_NORMAL)
                .compassEnabled(true)
                .useViewLifecycleInFragment(true);

        SupportMapFragment mapFragment = SupportMapFragment.newInstance(gmo);

        getParentFragmentManager().beginTransaction()
                .add(R.id.map_frag_cont, mapFragment)
                .commit();

        mapFragment.getMapAsync(this);

    }

    private void inputFieldFocusManage() {

        binding.town.setOnEditorActionListener((textView, i, keyEvent) -> {
            AppUtil.closeKeyboard(requireActivity());
            binding.town.clearFocus();
            binding.stateSpinner.show();
            return true;
        });

        binding.zipCode.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                if (editable.toString().length() == 6) {
                    binding.zipCode.clearFocus();
                    AppUtil.closeKeyboard(requireActivity());
                }
            }
        });

    }

    // load data from watcher
    private void loadPatientAddressSavedState() throws Exception {
        PatientData patientData = viewModel.getPatientData();

        if (patientData.country != null) {
            binding.countrySpinner.selectItemByIndex(countryList.indexOf(patientData.country));

            if (patientData.state != null) {
                binding.stateSpinner.selectItemByIndex(country_N_states_map.get(patientData.country).indexOf(patientData.state));
            }
        }

        if (patientData.lat != null && patientData.lng != null) {
            try {
                double lat = Double.parseDouble(patientData.lat);
                double lng = Double.parseDouble(patientData.lng);

                currentLocation = new LatLng(lat, lng);
            } catch (NumberFormatException e) {
                // do nothing
            }
        }

        if (patientData.address_line1 != null) {
            binding.street.setText(patientData.address_line1);
        }

        if (patientData.city != null) {
            binding.town.setText(patientData.city);
        }

        if (patientData.post_code != null) {
            binding.zipCode.setText(patientData.post_code);
        }
    }

    private void registerMapSearchResultLauncher() {

        // initializing places

        // Initialize the SDK
        Places.initialize(requireContext(), getString(R.string.GOOGLE_MAPS_API_KEY));

        startAutocompleteMapSearch = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        Intent intent = result.getData();
                        if (intent != null) {
                            Place place = Autocomplete.getPlaceFromIntent(intent);

                            if (place.getLatLng() == null) return;

                            // animating camera to selected location
                            CameraPosition camPos = new CameraPosition.Builder()
                                    .target(place.getLatLng())
                                    .zoom(15)
                                    .build();

                            googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(camPos));

                            // updating current location and adding a land mark
                            addMarker(place.getLatLng(), place.getName());

                        }
                    } else if (result.getResultCode() == Activity.RESULT_CANCELED) {
                        // The user canceled the operation.
                        Log.d(TAG, "User canceled autocomplete");
                    }
                });


    }

    private void clickEvents() {

        binding.backBtn.setOnClickListener(v -> {
            if (getActivity() != null) {
                getActivity().onBackPressed();
            }
        });

        binding.submit.setOnClickListener(v ->
                {
                    if (allOkay()) {

                        PatientData patientData = viewModel.getPatientData();

                        patientData.address_line1 = binding.street.getText().toString().trim();
                        patientData.city = binding.town.getText().toString();
                        patientData.post_code = binding.zipCode.getText().toString();

                        try {
                            patientData.country = countryList.get(binding.countrySpinner.getSelectedIndex());
                        } catch (Exception e) {
                            patientData.country = "";
                        }

                        try {
                            patientData.state = country_N_states_map.get(patientData.country).get(binding.stateSpinner.getSelectedIndex());
                        } catch (Exception e) {
                            patientData.state = "";
                        }

                        patientData.lat = String.valueOf(currentLocation.latitude);
                        patientData.lng = String.valueOf(currentLocation.longitude);

                        Navigation.findNavController(v).navigate(R.id.action_locationFragment_to_createPinFragment);
                    }
                }
        );

        binding.search.setOnClickListener(v -> {

            List<Place.Field> fields = Arrays.asList(Place.Field.ID
                    , Place.Field.NAME,
                    Place.Field.LAT_LNG, Place.Field.ADDRESS);

            Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)
                    .build(requireContext());

            startAutocompleteMapSearch.launch(intent);
        });

        binding.street.addTextChangedListener(TextUtils.LEADING_SPACE_WATCHER);
        new EditTextErrorRemover(binding.street);

    }

    private boolean allOkay() {
        boolean allOkay = true;

//        if (binding.street.getText().toString().trim().isEmpty()) {
//            allOkay = false;
//            binding.street.setError("Required");
//        }

//        if (binding.town.getText().toString().trim().isEmpty()) {
////            allOkay = false;
////            binding.town.setError("Required");
//            Toast.makeText(requireContext(), "Select a valid location.", Toast.LENGTH_SHORT).show();
//            return false;
//        }
//
//        if (binding.zipCode.getText().toString().trim().isEmpty()) {
////            allOkay = false;
////            binding.zipCode.setError("Required");
//            Toast.makeText(requireContext(), "Select a valid location.", Toast.LENGTH_SHORT).show();
//            return false;
//        }
//
//        if (binding.countrySpinner.getSelectedIndex() == -1) {
////            allOkay = false;
////            binding.countrySpinner.setError("Required");
//            Toast.makeText(requireContext(), "Select a valid location.", Toast.LENGTH_SHORT).show();
//            return false;
//        }
//
//        if (binding.stateSpinner.getSelectedIndex() == -1) {
////            allOkay = false;
////            binding.stateSpinner.setError("Required");
//            Toast.makeText(requireContext(), "Select a valid location.", Toast.LENGTH_SHORT).show();
//            return false;
//        }

        if (currentLocation == null && allOkay) {
            allOkay = false;
            Toast.makeText(requireContext(), "Please click on map to select a location.", Toast.LENGTH_SHORT).show();
        }

        return allOkay;
    }

    private void getLocationPermission() {
        // launching request to location permissions
        registerForActivityResult(new ActivityResultContracts
                        .RequestMultiplePermissions(), result -> {

                    Boolean fineLocationGranted;

                    if (result.containsKey(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        fineLocationGranted = result.get(Manifest.permission.ACCESS_FINE_LOCATION);
                    } else {
                        fineLocationGranted = false;
                    }

                    Boolean coarseLocationGranted;

                    if (result.containsKey(Manifest.permission.ACCESS_COARSE_LOCATION)) {
                        coarseLocationGranted = result.get(Manifest.permission.ACCESS_COARSE_LOCATION);
                    } else {
                        coarseLocationGranted = false;
                    }

                    if ((fineLocationGranted != null && fineLocationGranted) || (coarseLocationGranted != null && coarseLocationGranted)) {
                        if (currentLocation == null) {
                            // one of the location access granted.
                            Toast.makeText(requireContext(), "Fetching your current location", Toast.LENGTH_SHORT).show();
                            if (googleMap != null) {
                                if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                                    return;
                                }
                                googleMap.setMyLocationEnabled(true);
                                googleMap.getUiSettings().setMyLocationButtonEnabled(true);
                            }

                            requestLocations();
                        }
                    }
                }
        ).launch(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION});
    }

    @Override
    public void onLocationChanged(@NonNull Location location) {
        if (currentLocation == null) {
            this.currentLocation = new LatLng(location.getLatitude(), location.getLongitude());
            fusedLocationProviderClient.removeLocationUpdates(this);
            Log.d(TAG, "onLocationChanged: location updates removed");

            CameraPosition default_map_pos = new CameraPosition.Builder()
                    .target(currentLocation)
                    .zoom(15)
                    .build();

            googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(default_map_pos));
        }
    }

    @Override
    public void onMapReady(@NonNull GoogleMap googleMap) {
        this.googleMap = googleMap;

        if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            googleMap.setMyLocationEnabled(true);
            googleMap.getUiSettings().setMyLocationButtonEnabled(true);
        }

        googleMap.setOnMapClickListener(this);

        if (currentLocation != null) {
            addMarker(currentLocation, null);
            googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLocation, 14));
        } else {
            googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(40.75796541422796, -73.98557368665934), 14));
        }
    }

    @Override
    public void onMapClick(@NonNull LatLng latLng) {
        addMarker(latLng, null);
    }

    private void addMarker(LatLng latLng, String title) {
        // updating current location
        this.currentLocation = latLng;
        if (marker != null && marker.isVisible()) marker.remove();
        marker = null;
        marker = googleMap.addMarker(new MarkerOptions()
                .position(latLng).draggable(false));

        try {
            // fetching address from the lag lng
            Geocoder geocoder = new Geocoder(requireContext(), Locale.getDefault());
            List<Address> addresses = geocoder.getFromLocation(latLng.latitude, latLng.longitude, 1);

            if (addresses != null && addresses.size() > 0 && addresses.get(0) != null) {
                Address address = addresses.get(0);

                binding.zipCode.setText(address.getPostalCode());

                if (title != null) {
                    if (marker != null) marker.setTitle(title);

                    binding.street.setText(title);
                } else if (address.getThoroughfare() == null || address.getThoroughfare().isEmpty()) {
                    binding.street.setText(null);
                } else {
                    binding.street.setText(address.getThoroughfare());
                }

                if (address.getSubLocality() != null) {
                    binding.town.setText(address.getSubLocality());
                } else {
                    binding.town.setText(address.getLocality());
                }

                if (countryList == null) return;

                if (countryList.contains(address.getCountryName())) {
                    int index = countryList.indexOf(address.getCountryName());

                    if (index >= 0 && index < countryList.size()) {
                        binding.countrySpinner.selectItemByIndex(index);

                        ArrayList<String> stateList = country_N_states_map.get(address.getCountryName());

                        if (stateList == null || stateList.isEmpty()) return;

                        int state_index = stateList.indexOf(address.getAdminArea());

                        if (state_index >= 0 && state_index < stateList.size()) {
                            binding.stateSpinner.selectItemByIndex(state_index);
                        }
                    }
                } else {
                    binding.countrySpinner.clearSelectedItem();
                }

            } else {
                binding.countrySpinner.clearSelectedItem();
                binding.street.setText(null);
                binding.stateSpinner.clearSelectedItem();
                binding.zipCode.setText(null);
                binding.town.setText(null);
            }

        } catch (Exception e) {
            // do nothing as we couldn't load the location from the lat lng
        }

        binding.addressView.setVisibility(View.VISIBLE);

        binding.street.clearFocus();
        binding.scrollView.fullScroll(ScrollView.FOCUS_DOWN);

    }

    private void requestLocations() {
        if (!isLocationProviderEnabled(requireContext())) {
            AppUtil.showAlert(requireContext(),
                    getString(R.string.GPS_ENABLE_REQUIRED),
                    getString(R.string.GPS_ENABLE_MESSAGE),
                    getString(R.string.SETTINGS),
                    (dialog, which) -> {
                        // Settings button click callback
                        // Sending user to turn location on

                        Intent intent = new Intent();
                        intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);

                        startActivity(intent);
                    },
                    getString(R.string.NOT_NOW),
                    (dialog, which) -> dialog.dismiss()
            );
            return;
        }

        if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            getLocationPermission();
            return;
        }

        fusedLocationProviderClient.requestLocationUpdates(locationRequest, this, null);
    }

    public boolean isLocationProviderEnabled(Context context) {
        LocationManager locationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE);
        return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    }

    // loads all countries and their states into the spinners
    private void loadCountriesAndStates() {

        countryList = new ArrayList<>();

        country_N_states_map = new HashMap<>();

        try {

            String country_n_states_str = readCountryNStates(requireContext());

            JSONArray country_n_states = new JSONObject(country_n_states_str).getJSONArray("countries");

            Log.d(TAG, "loadCountriesAndStates: " + country_n_states);

            for (int i = 0; i < country_n_states.length(); i++) {

                String country = country_n_states.getJSONObject(i).getString("country");
                countryList.add(country);

                JSONArray states = country_n_states.getJSONObject(i).getJSONArray("states");

                ArrayList<String> stateList = new ArrayList<>();

                for (int j = 0; j < states.length(); j++) {
                    stateList.add(states.getString(j));
                }

                country_N_states_map.put(country, stateList);
            }

        } catch (Exception e) {

            Log.e(TAG, "load country and states: ", e);
        }

        Log.d(TAG, "loadCountriesAndStates: " + countryList);

        binding.stateSpinner.setLifecycleOwner(this);
        binding.countrySpinner.setLifecycleOwner(this);

        binding.countrySpinner.setItems(countryList);

        binding.stateSpinner.setDismissWhenNotifiedItemSelected(true);
        binding.countrySpinner.setDismissWhenNotifiedItemSelected(true);

        binding.stateSpinner.setIsFocusable(true);
        binding.countrySpinner.setIsFocusable(true);

        binding.countrySpinner.setOnSpinnerItemSelectedListener((OnSpinnerItemSelectedListener<String>) (i, s, i1, t1) -> {

            binding.countrySpinner.setError(null);

            ArrayList<String> stateList;

            if (country_N_states_map.containsKey(t1) && country_N_states_map.get(t1) != null) {
                stateList = country_N_states_map.get(t1);
            } else {
                stateList = new ArrayList<>();
            }

            if (stateList != null) {
                binding.stateSpinner.setItems(stateList);
                binding.stateSpinner.clearSelectedItem();
            }

//            binding.street.requestFocus();
//
//            if (getActivity() != null) {
//                getActivity().getWindow()
//                        .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
//            }
        });

//        binding.stateSpinner.setOnSpinnerItemSelectedListener((OnSpinnerItemSelectedListener<String>) (i, s, i1, t1) -> {
//            binding.stateSpinner.setError(null);
//            binding.zipCode.requestFocus();
//
//            if (getActivity() != null) {
//                getActivity().getWindow()
//                        .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
//            }
//        });

    }

    public String readCountryNStates(Context context) throws IOException {
        StringBuilder returnString = new StringBuilder();

        InputStream fIn = context.getResources().openRawResource(R.raw.country_n_states);
        InputStreamReader isr = new InputStreamReader(fIn);
        BufferedReader input = new BufferedReader(isr);

        String line = "";

        while ((line = input.readLine()) != null) {
            returnString.append(line);
        }

        try {
            isr.close();

            if (fIn != null) fIn.close();

            input.close();

        } catch (Exception e) {
            Log.e(TAG, "readCountryNStates: ", e);
        }

        return returnString.toString();
    }
}
