package com.wayz.location.wz_location_plugin

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.wayz.location.MapsInitializer
import com.wayz.location.WzException
import com.wayz.location.WzLocation
import com.wayz.location.WzLocationClient
import com.wayz.location.WzLocationClientOption
import com.wayz.location.WzLocationListener
import com.wayz.location.WzPlace
import com.wayz.location.WzTag
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result

class WzLocationPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

  companion object {
    private const val TAG = "WzLocationPlugin"
    private const val MAX_PENDING_EVENTS = 16
  }

  private lateinit var channel: MethodChannel
  private lateinit var eventChannel: EventChannel
  private lateinit var applicationContext: Context
  /** Privacy dialogs require a window token; [applicationContext] is not sufficient. */
  private var activity: Activity? = null
  private val locationOption = WzLocationClientOption()
  private var locationClient: WzLocationClient? = null
  private var eventSink: EventChannel.EventSink? = null
  private val pendingEvents = ArrayDeque<Map<String, Any?>>(MAX_PENDING_EVENTS)
  private val pendingLock = Any()
  private val mainHandler = Handler(Looper.getMainLooper())

  private val locationListener = object : WzLocationListener {
    override fun onLocationReceived(location: WzLocation) {
      Log.d(
        TAG,
        "onLocationReceived lat=${location.latitude} lng=${location.longitude} " +
          "accuracy=${location.accuracy} provider=${location.provider} time=${location.time}",
      )
      postLocationToSink(wzLocationToMap(location))
    }

    override fun onLocationError(exception: WzException) {
      Log.e(
        TAG,
        "onLocationError code=${exception.errorCode} message=${exception.errorMessage}",
      )
      postToSink(
        mapOf(
          "type" to "error",
          "errorCode" to exception.errorCode,
          "errorMessage" to (exception.errorMessage ?: ""),
        ),
      )
    }
  }

  override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    applicationContext = flutterPluginBinding.applicationContext
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wz_location_plugin")
    channel.setMethodCallHandler(this)

    eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "wz_location_plugin_stream")
    eventChannel.setStreamHandler(
      object : EventChannel.StreamHandler {
        override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
          eventSink = events
          mainHandler.post { flushPendingToFlutter() }
        }

        override fun onCancel(arguments: Any?) {
          eventSink = null
          synchronized(pendingLock) { pendingEvents.clear() }
        }
      },
    )
  }

  override fun onAttachedToActivity(binding: ActivityPluginBinding) {
    activity = binding.activity
  }

  override fun onDetachedFromActivityForConfigChanges() {
    activity = null
  }

  override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
    activity = binding.activity
  }

  override fun onDetachedFromActivity() {
    activity = null
  }

  private fun activityForPrivacyUi(result: Result): Activity? {
    val a = activity
    if (a == null) {
      result.error(
        "no_activity",
        "Privacy UI requires an attached Activity; call after the Flutter Activity is visible.",
        null,
      )
      return null
    }
    if (a.isFinishing) {
      result.error("no_activity", "Activity is finishing; cannot show privacy UI.", null)
      return null
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && a.isDestroyed) {
      result.error("no_activity", "Activity is destroyed; cannot show privacy UI.", null)
      return null
    }
    return a
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    when (call.method) {
      "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}")
      "setApiKey" -> handleSetApiKey(call, result)
      "updatePrivacyShow" -> handleUpdatePrivacyShow(call, result)
      "updatePrivacyAgree" -> handlePrivacyAgree(call, result)
      "setLocationOption" -> handleSetLocationOption(call, result)
      "startLocation" -> handleStartLocation(result)
      "stopLocation" -> handleStopLocation(result)
      else -> result.notImplemented()
    }
  }

  private fun postToSink(payload: Map<String, Any?>) {
    val out = HashMap<String, Any>(payload.size)
    for ((k, v) in payload) {
      if (v != null) {
        out[k] = v
      }
    }
    mainHandler.post { deliverLocationEvent(out) }
  }

  /** Location payloads preserve null values for a stable key set on the Dart side. */
  private fun postLocationToSink(payload: Map<String, Any?>) {
    mainHandler.post { deliverLocationEvent(payload) }
  }

  /** Deliver to Flutter or queue until [eventSink] exists (avoids silent loss on race). */
  private fun deliverLocationEvent(out: Map<String, Any?>) {
    val sink = eventSink
    if (sink != null) {
      flushPendingToFlutter()
      sink.success(out)
      return
    }
    synchronized(pendingLock) {
      while (pendingEvents.size >= MAX_PENDING_EVENTS) {
        pendingEvents.removeFirst()
      }
      pendingEvents.addLast(out)
    }
    Log.w(TAG, "Flutter has no EventChannel listener yet; queued location event (size=${pendingEvents.size}). Subscribe to onLocationChanged early.")
  }

  private fun wzPlaceToMap(place: WzPlace?): Map<String, Any?>? {
    if (place == null) return null
    return mapOf(
      "id" to place.id,
      "name" to place.name,
      "categoryId" to place.categoryId,
      "categoryName" to place.categoryName,
      "type" to place.type,
      "distance" to place.distance,
    )
  }

  private fun wzTagToMap(tag: WzTag): Map<String, Any?> =
    mapOf(
      "id" to tag.id,
      "name" to tag.name,
    )

  @SuppressLint("NewApi")
  private fun wzLocationToMap(loc: WzLocation): Map<String, Any?> {
    val businessAreas =
      loc.businessAreas?.mapNotNull { wzPlaceToMap(it) } ?: emptyList<Map<String, Any?>>()
    val nearbyPlaces =
      loc.nearbyPlaces?.mapNotNull { wzPlaceToMap(it) } ?: emptyList<Map<String, Any?>>()
    val tags = loc.tags?.map { wzTagToMap(it) } ?: emptyList<Map<String, Any?>>()

    val out = LinkedHashMap<String, Any?>()
    out["type"] = "location"
    out["latitude"] = loc.latitude
    out["longitude"] = loc.longitude
    out["altitude"] = loc.altitude
    out["speed"] = loc.speed.toDouble()
    out["bearing"] = loc.bearing.toDouble()
    out["accuracy"] = loc.accuracy.toDouble()
    out["confidence"] = loc.confidence.toDouble()
    out["hasAltitude"] = loc.hasAltitude()
    out["hasSpeed"] = loc.hasSpeed()
    out["hasBearing"] = loc.hasBearing()
    out["hasAccuracy"] = loc.hasAccuracy()
    out["provider"] = loc.provider
    out["time"] = loc.time
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      out["elapsedRealtimeNanos"] = loc.elapsedRealtimeNanos
    } else {
      out["elapsedRealtimeNanos"] = null
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      out["hasVerticalAccuracy"] = loc.hasVerticalAccuracy()
      out["verticalAccuracyMeters"] =
        if (loc.hasVerticalAccuracy()) loc.verticalAccuracyMeters.toDouble() else null
      out["hasSpeedAccuracy"] = loc.hasSpeedAccuracy()
      out["speedAccuracyMetersPerSecond"] =
        if (loc.hasSpeedAccuracy()) loc.speedAccuracyMetersPerSecond.toDouble() else null
      out["hasBearingAccuracy"] = loc.hasBearingAccuracy()
      out["bearingAccuracyDegrees"] =
        if (loc.hasBearingAccuracy()) loc.bearingAccuracyDegrees.toDouble() else null
    } else {
      out["hasVerticalAccuracy"] = null
      out["verticalAccuracyMeters"] = null
      out["hasSpeedAccuracy"] = null
      out["speedAccuracyMetersPerSecond"] = null
      out["hasBearingAccuracy"] = null
      out["bearingAccuracyDegrees"] = null
    }
    out["address"] = loc.address
    out["addressDescription"] = loc.addressDescription
    out["country"] = loc.country
    out["countryCode"] = loc.countryCode
    out["provinceCode"] = loc.provinceCode
    out["province"] = loc.province
    out["city"] = loc.city
    out["cityCode"] = loc.cityCode
    out["district"] = loc.district
    out["districtCode"] = loc.districtCode
    out["township"] = loc.township
    out["street"] = loc.street
    out["houseNumber"] = loc.houseNumber
    out["postCode"] = loc.postCode
    out["id"] = loc.id
    out["source"] = loc.source
    out["room"] = loc.room
    out["floor"] = loc.floor
    out["buildingId"] = loc.buildingId
    out["buildingName"] = loc.buildingName
    out["boundingArea"] = wzPlaceToMap(loc.boundingArea)
    out["scenario"] = wzPlaceToMap(loc.scenario)
    out["place"] = wzPlaceToMap(loc.place)
    out["subScene"] = wzPlaceToMap(loc.subScene)
    out["mainScene"] = wzPlaceToMap(loc.mainScene)
    out["businessAreas"] = businessAreas
    out["nearbyPlaces"] = nearbyPlaces
    out["tags"] = tags
    return out
  }

  private fun flushPendingToFlutter() {
    val sink = eventSink ?: return
    synchronized(pendingLock) {
      while (pendingEvents.isNotEmpty()) {
        sink.success(pendingEvents.removeFirst())
      }
    }
  }

  private fun handleSetApiKey(call: MethodCall, result: Result) {
    val args = call.arguments as? Map<*, *>
    val apiKey = args?.get("apiKey") as? String
    if (apiKey == null) {
      result.error("invalid_args", "apiKey is required", null)
      return
    }
    locationOption.setApiKey(apiKey)
    // 开启SDK日志
    MapsInitializer.setLogEnabled(true)

    // 设置日志回调,获取SDK日志
    MapsInitializer.setLogCallback { level, tag, message ->
      Log.d(
        TAG,
        message
      )
    }
    locationClient?.setLocationOption(locationOption)
    result.success(true)
  }

  private fun handleUpdatePrivacyShow(call: MethodCall, result: Result) {
    val args = call.arguments as? Map<*, *>
    val hasContains = args?.get("hasContains") as? Boolean
    val hasShow = args?.get("hasShow") as? Boolean
    if (hasContains == null || hasShow == null) {
      result.error("invalid_args", "hasContains and hasShow are required", null)
      return
    }
    val ctx = activityForPrivacyUi(result) ?: return
    MapsInitializer.updatePrivacyShow(ctx, hasContains, hasShow)
    result.success(true)
  }

  private fun handlePrivacyAgree(call: MethodCall, result: Result) {
    val args = call.arguments as? Map<*, *>
    val hasAgree = args?.get("hasAgree") as? Boolean
    if (hasAgree == null) {
      result.error("invalid_args", "hasAgree is required", null)
      return
    }
    val ctx = activityForPrivacyUi(result) ?: return
    MapsInitializer.updatePrivacyAgree(ctx, hasAgree)
    result.success(true)
  }

  private fun handleSetLocationOption(call: MethodCall, result: Result) {
    val args = call.arguments as? Map<*, *> ?: run {
      result.error("invalid_args", "arguments must be a map", null)
      return
    }
    val apiKey = args["apiKey"] as? String
    if (apiKey != null) {
      locationOption.setApiKey(apiKey)
    }
    val interval = (args["interval"] as? Number)?.toInt() ?: 5000
    locationOption.interval = interval
    val isLocateOnce = args["isLocateOnce"] as? Boolean ?: false
    locationOption.setOnceLocate(isLocateOnce)
    if (locationClient == null) {
      locationClient = WzLocationClient(applicationContext, locationOption)
    }else{
      locationClient?.setLocationOption(locationOption)
    }
    result.success(true)
  }

  private fun handleStartLocation(result: Result) {
    if (locationClient == null) {
      locationClient = WzLocationClient(applicationContext, locationOption)
    }
    locationClient!!.startLocation(locationListener)
    result.success(true)
  }

  private fun handleStopLocation(result: Result) {
    locationClient?.stopLocation()
    result.success(true)
  }

  override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
    eventChannel.setStreamHandler(null)
    eventSink = null
    synchronized(pendingLock) { pendingEvents.clear() }
    locationClient?.onDestroy()
    locationClient = null
  }
}
