package com.za.service.location

import android.annotation.SuppressLint
import android.content.Context
import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import android.os.Build
import androidx.core.content.ContextCompat
import androidx.core.location.LocationManagerCompat
import com.amap.api.location.AMapLocation
import com.amap.api.location.AMapLocationClient
import com.amap.api.location.AMapLocationClientOption
import com.amap.api.location.AMapLocationListener
import com.amap.api.services.core.LatLonPoint
import com.amap.api.services.geocoder.GeocodeResult
import com.amap.api.services.geocoder.GeocodeSearch
import com.amap.api.services.geocoder.GeocodeSearch.OnGeocodeSearchListener
import com.amap.api.services.geocoder.RegeocodeQuery
import com.amap.api.services.geocoder.RegeocodeResult
import com.blankj.utilcode.util.NetworkUtils
import com.za.bean.request.UploadGpsRequest
import com.za.common.GlobalData
import com.za.common.log.LogUtil
import com.za.ext.toJson
import com.za.net.CommonMethod
import com.za.service.mqtt.MyMqttClient
import java.util.Locale
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt

@SuppressLint("StaticFieldLeak")
object ZdLocationManager : AMapLocationListener {
	private const val TAG = "ZdLocationManager"
	private var aMapLocationClient : AMapLocationClient? = null
	private var context : Context? = null
	private var option : AMapLocationClientOption? = null

	private const val NORMA_INTERVAL_TIME = 20000L // 普通的GPS上传时间间隔
	private const val LOCATION_CACHE_DURATION = 60 * 1000L // 1分钟的缓存时间
	private const val LOCATION_SEARCH_RADIUS = 200f
	private const val DEFAULT_TIMEOUT = 30000L

	private var globalLocation : AMapLocation? = null
	private var lastSingleLocationTime = 0L

	fun init(context : Context) {
		this.context = context.applicationContext
		initAMapLocationClient()
	}

	private fun initAMapLocationClient() {
		context?.let { ctx ->
			try {
				AMapLocationClient.updatePrivacyShow(ctx, true, true)
				AMapLocationClient.updatePrivacyAgree(ctx, true)
				option = createLocationOption()
				aMapLocationClient = AMapLocationClient(ctx).apply {
					setLocationOption(option)
					setLocationListener(this@ZdLocationManager)
				}
			} catch (e : Exception) {
				LogUtil.print(TAG, "初始化定位失败: ${e.message}")
			}
		}
	}

	private fun createLocationOption(isOnceLocation : Boolean = false,
		isGpsFirst : Boolean = false,
		isNeedAddress : Boolean = false) : AMapLocationClientOption =
		AMapLocationClientOption().apply {
			locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy
			interval = NORMA_INTERVAL_TIME
			isWifiScan = true
			isLocationCacheEnable = false
			httpTimeOut = DEFAULT_TIMEOUT
			isMockEnable = false
			this.isNeedAddress = isNeedAddress
			this.isOnceLocation = isOnceLocation
			this.isGpsFirst = isGpsFirst
		}

	fun startContinuousLocation(context : Context) {
		stopContinuousLocation()
		if (aMapLocationClient == null) {
			init(context)
		}
		try {
			aMapLocationClient?.startLocation()
			LogUtil.print(TAG, "持续定位开启成功")
		} catch (e : Exception) {
			LogUtil.print(TAG, "持续定位开启失败: ${e.message}")
		}
	}

	fun stopContinuousLocation() {
		try {
			aMapLocationClient?.stopLocation()
			aMapLocationClient?.onDestroy()
			aMapLocationClient?.unRegisterLocationListener(this)
			aMapLocationClient = null
			LogUtil.print(TAG, "关闭持续定位成功")
		} catch (e : Exception) {
			LogUtil.print(TAG, "关闭持续定位失败: ${e.message}")
		}
	}

	override fun onLocationChanged(location : AMapLocation?) {
		if (location == null) {
			LogUtil.print(TAG, "定位获取失败: location is null")
			return
		}

		if (location.errorCode != 0) {
			LogUtil.print(TAG, "定位获取失败: ${location.errorInfo}")
			return
		}

		GlobalData.currentLocation = location
		uploadGpsRequest(location)
	}

	private fun uploadGpsRequest(location : AMapLocation) {
		val request = UploadGpsRequest().apply {
			lat = location.latitude
			lng = location.longitude
			userId = GlobalData.driverInfoBean?.userId
			vehicleId = GlobalData.driverInfoBean?.vehicleId
			working = GlobalData.currentOrder != null
			direction = location.bearing.toDouble()
			speed = location.speed.toDouble()
		}

		uploadGps(request)
	}

	private fun uploadGps(uploadGpsRequest : UploadGpsRequest) {
		if (GlobalData.token.isNullOrBlank()) {
			LogUtil.print(TAG, "定位上传失败: token is null,request=$uploadGpsRequest")
			return
		}
		CommonMethod.uploadGps(uploadGpsRequest, success = {
			LogUtil.print(TAG, "定位上传成功: ${uploadGpsRequest.toJson()}")
			MyMqttClient.publishMessage() //			if (ActivityUtils.getTopActivity()==null) {
			//				AppUtils.launchApp(AppUtils.getAppPackageName())
			//			}
		}, failed = { error ->
			LogUtil.print(TAG, "定位上传失败: $error  $uploadGpsRequest")
			MyMqttClient.publishMessage()
		})
	}

	fun getSingleLocation(success : (AMapLocation) -> Unit,
		failed : (String) -> Unit,
		isNeedAddress : Boolean = false) {
		globalLocation?.let { location ->
			if (System.currentTimeMillis() - lastSingleLocationTime < LOCATION_CACHE_DURATION && (! isNeedAddress || ! location.address.isNullOrBlank())) {
				LogUtil.print(TAG, "返回缓存位置")
				success(location)
				GlobalData.currentLocation = location
				return
			}
		}

		NetworkUtils.isAvailableAsync { isNetworkAvailable ->
			LogUtil.print(TAG, "网络状态: $isNetworkAvailable")
			if (isNetworkAvailable) {
				doGetSingleLocationOnline(isNeedAddress = isNeedAddress,
					success = success,
					failed = failed)
			} else {
				doGetSingleLocationGpsFromOriginal(success,
					failed) //				doGetSingleLocationGps(success, failed)
			}
		}
	}

	private fun createSingleLocationClient(isNeedAddress : Boolean,
		isGpsFirst : Boolean,
		locationCallback : (AMapLocation?) -> Unit) : AMapLocationClient? {
		return context?.let { ctx ->
			try {
				AMapLocationClient(ctx).apply {
					setLocationOption(createLocationOption(isOnceLocation = true,
						isGpsFirst = isGpsFirst,
						isNeedAddress = isNeedAddress))
					setLocationListener { location ->
						locationCallback(location)
						stopLocation()
						onDestroy()
					}
				}
			} catch (e : Exception) {
				LogUtil.print(TAG, "创建单次定位客户端失败: ${e.message}")
				null
			}
		}
	}

	private fun handleLocationResult(location : AMapLocation?,
		isNeedAddress : Boolean,
		success : (AMapLocation) -> Unit,
		failed : (String) -> Unit) {
		if (location == null) {
			failed("Location is null")
			LogUtil.print(TAG, "单次定位失败: location is null")
			return
		}

		if (location.errorCode != 0) {
			failed("Error code: ${location.errorCode}, ${location.errorInfo}")
			LogUtil.print(TAG,
				"单次定位失败: errorCode=${location.errorCode}, errorInfo=${location.locationDetail}")
			return
		}

		if (isNeedAddress && location.address.isNullOrBlank()) {
			nativeGeoCoder(location = location, success = { success(it) }, failed = { error ->
				LogUtil.print(TAG, "逆地理编码失败: $error")
				failed("位置获取失败")
			})
		} else {
			updateGlobalLocation(location)
			success(location)
		}
	}

	private fun updateGlobalLocation(location : AMapLocation) {
		globalLocation = location
		lastSingleLocationTime = System.currentTimeMillis()
		GlobalData.currentLocation = location
		LogUtil.print(TAG, "单次定位结果: $location")
	}

	private fun doGetSingleLocationOnline(isNeedAddress : Boolean,
		success : (AMapLocation) -> Unit,
		failed : (String) -> Unit) {
		val locationClient = createSingleLocationClient(isNeedAddress = isNeedAddress,
			isGpsFirst = false) { location ->
			handleLocationResult(location, isNeedAddress, success, failed)
		} ?: run {
			failed("Failed to create location client")
			return
		}

		try {
			locationClient.startLocation()
		} catch (e : Exception) {
			LogUtil.print(TAG, "启动单次定位失败: ${e.message}")
			failed("Failed to start location: ${e.message}")
		}
	}

	private fun doGetSingleLocationGps(success : (AMapLocation) -> Unit,
		failed : (String) -> Unit) {
		val locationClient =
			createSingleLocationClient(isNeedAddress = false, isGpsFirst = true) { location ->
				handleLocationResult(location, false, success, failed)
			} ?: run {
				failed("Failed to create location client")
				return
			}

		try {
			locationClient.startLocation()
		} catch (e : Exception) {
			LogUtil.print(TAG, "启动GPS定位失败: ${e.message}")
			failed("Failed to start GPS location: ${e.message}")
		}
	}

	@SuppressLint("MissingPermission")
	private fun doGetSingleLocationGpsFromOriginal(success : (AMapLocation) -> Unit,
		failed : (String) -> Unit) {
		context?.let {
			try {
				val locationManager =
					ContextCompat.getSystemService(it, LocationManager::class.java)
				if (locationManager == null || ! LocationManagerCompat.isLocationEnabled(
						locationManager)
				) {
					failed("Location service is disabled")
					return@let
				}
				val lastKnownLocation =
					locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
				if (lastKnownLocation != null) {
					val gcj02Location =
						wgs84ToGcj02(lastKnownLocation.latitude, lastKnownLocation.longitude)
					val aMapLocation = AMapLocation("").apply {
						latitude = gcj02Location.latitude
						longitude = gcj02Location.longitude
						accuracy = lastKnownLocation.accuracy
						bearing = lastKnownLocation.bearing
						speed = lastKnownLocation.speed
						time = lastKnownLocation.time
						errorCode = 0
					}
					updateGlobalLocation(aMapLocation)
					success(aMapLocation)
				} else {                // 如果没有最后位置，尝试请求单次更新
					requestSingleGpsUpdate(locationManager, success, failed)
				}
			} catch (e : Exception) {
				failed("Native GPS location failed: ${e.message}")
			}

		} ?: failed("获取失败")
	}

	@SuppressLint("MissingPermission")
	private fun requestSingleGpsUpdate(locationManager : LocationManager,
		success : (AMapLocation) -> Unit,
		failed : (String) -> Unit) {
		val locationListener = object : android.location.LocationListener {
			override fun onLocationChanged(location : Location) {
				locationManager.removeUpdates(this)
				val gcj02 = wgs84ToGcj02(location.latitude, location.longitude)
				val aMapLocation = AMapLocation("").apply {
					latitude = gcj02.latitude
					longitude = gcj02.longitude
					accuracy = location.accuracy
					bearing = location.bearing
					speed = location.speed
					time = location.time
					errorCode = 0
				}
				updateGlobalLocation(aMapLocation)
				success(aMapLocation)
			}

			override fun onProviderDisabled(provider : String) {
				locationManager.removeUpdates(this)
				failed("GPS provider disabled")
			}

			override fun onProviderEnabled(provider : String) {}
		}
		try {
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
				locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
					0L,
					0f,
					ContextCompat.getMainExecutor(context !!),
					locationListener)
			} else {
				locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
					0L,
					0f,
					locationListener)
			}
		} catch (e : Exception) {
			failed("Failed to request GPS update: ${e.message}")
		}
	}

	private fun geoCoder(aMapLocation : AMapLocation,
		success : (AMapLocation) -> Unit,
		failed : (String) -> Unit = {}) {
		try {
			val geocoderSearch = GeocodeSearch(context)
			geocoderSearch.setOnGeocodeSearchListener(object : OnGeocodeSearchListener {
				override fun onRegeocodeSearched(result : RegeocodeResult?, rCode : Int) {
					if (result == null || rCode != 1000) {
						failed("Geocode search failed: $rCode")
						return
					}

					val address = result.regeocodeAddress?.formatAddress
					if (! address.isNullOrBlank()) {
						aMapLocation.address = address
						success(aMapLocation)
					} else {
						failed("Empty address returned")
					}
				}

				override fun onGeocodeSearched(result : GeocodeResult?,
					rCode : Int) { // Not used in reverse geocoding
				}
			})

			val point = LatLonPoint(aMapLocation.latitude, aMapLocation.longitude)
			val query = RegeocodeQuery(point, LOCATION_SEARCH_RADIUS, GeocodeSearch.AMAP)
			geocoderSearch.getFromLocationAsyn(query)
		} catch (e : Exception) {
			LogUtil.print(TAG, "地理编码异常: ${e.message}")
			failed("Geocoding failed: ${e.message}")
		}
	}

	private fun nativeGeoCoder(location : AMapLocation,
		success : (AMapLocation) -> Unit,
		failed : (String) -> Unit) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
			context?.let { ctx ->
				try {
					val geocoder = Geocoder(ctx, Locale.getDefault())
					geocoder.getFromLocation(location.latitude,
						location.longitude,
						1) { addresses ->
						if (addresses.isNotEmpty()) {
							updateLocationWithNativeAddress(location, addresses[0])
							success(location)
						} else {
							failed("No address found")
						}
					}
				} catch (e : Exception) {
					LogUtil.print(TAG, "原生地理编码异常: ${e.message}")
					failed("Native geocoding failed: ${e.message}")
				}
			} ?: failed("Context is null")
		} else {
			context?.let { ctx ->
				try {
					val geocoder = Geocoder(ctx, Locale.getDefault())
					val addresses =
						geocoder.getFromLocation(location.latitude, location.longitude, 1)
					if (! addresses.isNullOrEmpty()) {
						updateLocationWithNativeAddress(location, addresses[0])
						success(location)
					} else {
						failed("No address found")
					}
				} catch (e : Exception) {
					LogUtil.print(TAG, "原生地理编码异常: ${e.message}")
					failed("Native geocoding failed: ${e.message}")
				}
			} ?: failed("Context is null")
		}
	}

	private fun updateLocationWithNativeAddress(location : AMapLocation,
		address : android.location.Address) {
		location.address = buildAddressString(address)
		location.country = address.countryName
		location.province = address.adminArea
		location.city = address.locality
		location.district = address.subLocality
		location.street = address.thoroughfare
	}

	private fun buildAddressString(address : android.location.Address) : String {
		return buildString {
			address.countryName?.let { append(it) }
			address.adminArea?.let { append(it) }
			address.locality?.let { append(it) }
			address.subLocality?.let { append(it) }
			address.thoroughfare?.let { append(it) }
			address.subThoroughfare?.let { append(it) }
		}
	}

	// WGS84转GCJ02(火星坐标)
	private fun wgs84ToGcj02(lat : Double, lon : Double) : WGS84Coordinate {
		if (isOutOfChina(lat, lon)) {
			return WGS84Coordinate(lat, lon)
		}

		var dLat = transformLat(lon - 105.0, lat - 35.0)
		var dLon = transformLon(lon - 105.0, lat - 35.0)

		val radLat = lat / 180.0 * Math.PI
		var magic = sin(radLat)
		magic = 1 - 0.006693421622965943 * magic * magic

		val sqrtMagic = sqrt(magic)
		dLat =
			(dLat * 180.0) / ((6378245.0 * (1 - 0.006693421622965943)) / (magic * sqrtMagic) * Math.PI)
		dLon = (dLon * 180.0) / (6378245.0 / sqrtMagic * cos(radLat) * Math.PI)

		return WGS84Coordinate(latitude = lat + dLat, longitude = lon + dLon)
	}

	data class WGS84Coordinate(val latitude : Double, val longitude : Double)

	private fun transformLat(x : Double, y : Double) : Double {
		var ret = - 100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(x))
		ret += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0
		ret += (20.0 * sin(y * Math.PI) + 40.0 * sin(y / 3.0 * Math.PI)) * 2.0 / 3.0
		ret += (160.0 * sin(y / 12.0 * Math.PI) + 320 * sin(y * Math.PI / 30.0)) * 2.0 / 3.0
		return ret
	}

	private fun transformLon(x : Double, y : Double) : Double {
		var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(x))
		ret += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0
		ret += (20.0 * sin(x * Math.PI) + 40.0 * sin(x / 3.0 * Math.PI)) * 2.0 / 3.0
		ret += (150.0 * sin(x / 12.0 * Math.PI) + 300.0 * sin(x / 30.0 * Math.PI)) * 2.0 / 3.0
		return ret
	}

	private fun isOutOfChina(lat : Double, lon : Double) : Boolean {
		return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271
	}
}