package net.pokeranalytics.android.util

import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.api.model.PlaceLikelihood
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest
import io.realm.Realm
import io.realm.kotlin.where
import net.pokeranalytics.android.model.realm.Location
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList

class LocationManager(private var context: Context) {

	companion object {
		const val MAXIMUM_DISTANCE_FROM_USER = 5000 // in meters
	}

	/**
	 * Return the [places] around the user
	 */
	fun askForPlacesRequest(callback: ((success: Boolean, places: ArrayList<PlaceLikelihood>) -> Unit)?) {

		// Initialize Places.
		Places.initialize(context, context.getString(net.pokeranalytics.android.R.string.google_places_api))

		// Create a new Places client instance.
		val placesClient = Places.createClient(context)

		// Use fields to define the data types to return.
		val placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS, Place.Field.LAT_LNG)

		// Use the builder to create a FindCurrentPlaceRequest.
		val request = FindCurrentPlaceRequest.builder(placeFields).build()

		// Call findCurrentPlace and handle the response (first check that the user has granted permission).
		if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
			val placeResponse = placesClient.findCurrentPlace(request)


			placeResponse.addOnCompleteListener { task ->
				val places = ArrayList<PlaceLikelihood>()
				if (task.isSuccessful) {
					val response = task.result
					response?.placeLikelihoods?.let {
						places.addAll(it)
					}
				} else {
					val exception = task.exception
					if (exception is ApiException) {
						Timber.d("Error: ${"Place not found: " + exception.statusCode}")
					}
				}

				callback?.invoke(task.isSuccessful, places)
			}
		} else {
			// If we don't have the permission, return
			callback?.invoke(false, ArrayList())
		}
	}

	/**
	 * Return if the database contains one or many locations with coordinates
	 */
	fun databaseContainsLocationsWithCoordinates(): Boolean {
		val realm = Realm.getDefaultInstance()
		val result = realm.where<Location>().isNotNull("latitude")
			.and().isNotNull("longitude").findAll().isNotEmpty()
		realm.close()
		return result
	}

	/**
	 * Find the nearest location from user
	 */
	fun findNearestLocationFromUser(callback: ((location: Location?) -> Unit)?) {

		val locations = ArrayList<net.pokeranalytics.android.model.realm.Location>()
		val realm = Realm.getDefaultInstance()
		val realmResults = realm.where<Location>().isNotNull("latitude")
			.and().isNotNull("longitude").findAll()
		locations.addAll(realm.copyFromRealm(realmResults))
		realm.close()

		// If we have no locations with coordinates, return null
		if (realmResults.size == 0) {
			callback?.invoke(null)
			return
		}

		val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)

		if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
			fusedLocationClient.lastLocation.addOnSuccessListener { location: android.location.Location? ->
				// Got last known location. In some rare situations this can be null.

				location?.let { currentLocation ->

					locations.sortBy {
						val locationToSort = android.location.Location("")
						locationToSort.latitude = it.latitude!!
						locationToSort.longitude = it.longitude!!
						currentLocation.distanceTo(locationToSort)
					}

					val nearestLocation = locations.firstOrNull()
					nearestLocation?.let {

						val locationForDistance = android.location.Location("")
						locationForDistance.latitude = it.latitude ?: 0.0
						locationForDistance.longitude = it.longitude ?: 0.0

						// Final check: if the distance between the location and the user is lower than MAXIMUM_DISTANCE_FROM_USER
						if (currentLocation.distanceTo(locationForDistance) < MAXIMUM_DISTANCE_FROM_USER) {
							callback?.invoke(nearestLocation)
						} else {
							callback?.invoke(null)
						}


					} ?: run {
						callback?.invoke(null)
					}

				} ?: run {
					// If the current location is null, return null
					callback?.invoke(null)
				}

			}.addOnCanceledListener {
				// If there was a problem during the call to last location, return null
				callback?.invoke(null)
			}
		} else {
			// If we don't have the permission, return null
			callback?.invoke(null)
		}
	}

	/**
	 * Return the current location of the user
	 */
	fun findCurrentLocation(callback: ((location: android.location.Location?) -> Unit)?) {
		val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
		if (ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
			fusedLocationClient.lastLocation.addOnSuccessListener { location: android.location.Location? ->
				// Got last known location. In some rare situations this can be null.
				location?.let { currentLocation ->
					callback?.invoke(currentLocation)
				} ?: run {
					// If the current location is null, return null
					callback?.invoke(null)
				}

			}.addOnCanceledListener {
				// If there was a problem during the call to last location, return null
				callback?.invoke(null)
			}

		} else {
			callback?.invoke(null)
		}
	}

}