import Flutter
import UIKit
import wzLib

private let maxPendingEvents = 16

public class WzLocationPlugin: NSObject, FlutterPlugin {
  private let bridge = WzLocationDelegateBridge()
  private lazy var client = WzClientLocation(delegate: bridge)

  private var eventSink: FlutterEventSink?
  private var pendingEvents: [[String: Any]] = []
  private let pendingLock = NSLock()

  public static func register(with registrar: FlutterPluginRegistrar) {
    let messenger = registrar.messenger()
    let channel = FlutterMethodChannel(name: "wz_location_plugin", binaryMessenger: messenger)
    let instance = WzLocationPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)

    let eventChannel = FlutterEventChannel(name: "wz_location_plugin_stream", binaryMessenger: messenger)
    eventChannel.setStreamHandler(WzLocationStreamHandler(plugin: instance))
  }

  fileprivate func attachSink(_ sink: FlutterEventSink?) {
    eventSink = sink
    if sink != nil {
      DispatchQueue.main.async { [weak self] in
        self?.flushPendingToFlutter()
      }
    }
  }

  fileprivate func cancelSink() {
    eventSink = nil
    pendingLock.lock()
    pendingEvents.removeAll()
    pendingLock.unlock()
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "getPlatformVersion":
      result("iOS " + UIDevice.current.systemVersion)
    case "setApiKey":
      handleSetApiKey(call, result: result)
    case "updatePrivacyShow":
      // iOS Wayz SDK 未提供与 Android MapsInitializer.updatePrivacyShow 对等的 API；保持调用方可移植性，此处视为成功。
      result(true)
    case "updatePrivacyAgree":
      handlePrivacyAgree(call, result: result)
    case "setLocationOption":
      handleSetLocationOption(call, result: result)
    case "startLocation":
      client.startLocation()
      result(true)
    case "stopLocation":
      client.stopLocation()
      result(true)
    default:
      result(FlutterMethodNotImplemented)
    }
  }

  fileprivate func emitLocationBridge() {
    bridge.onLocation = { [weak self] loc in
      self?.postLocationPayload(Self.locationResToFlutterMap(loc))
    }
    bridge.onError = { [weak self] msg in
      self?.postErrorPayload(msg)
    }
  }

  override init() {
    super.init()
    emitLocationBridge()
  }

  private func handleSetApiKey(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    guard let args = call.arguments as? [String: Any],
          let apiKey = args["apiKey"] as? String,
          !apiKey.isEmpty else {
      result(FlutterError(code: "invalid_args", message: "apiKey is required", details: nil))
      return
    }
    client.initKey(accesskey: apiKey)
    client.accesskey = apiKey
    result(true)
  }

  private func handlePrivacyAgree(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    guard let args = call.arguments as? [String: Any],
          let hasAgree = args["hasAgree"] as? Bool else {
      result(FlutterError(code: "invalid_args", message: "hasAgree is required", details: nil))
      return
    }
    client.setAgreePrivacy(isAgree: hasAgree)
    result(true)
  }

  private func handleSetLocationOption(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    guard let args = call.arguments as? [String: Any] else {
      result(FlutterError(code: "invalid_args", message: "arguments must be a map", details: nil))
      return
    }
    if let apiKey = args["apiKey"] as? String, !apiKey.isEmpty {
      client.initKey(accesskey: apiKey)
      client.accesskey = apiKey
    }
    if let interval = args["interval"] as? Int {
      client.interval = Double(interval) / 1000.0
    } else if let interval = args["interval"] as? Double {
      client.interval = interval / 1000.0
    }
    if let once = args["isLocateOnce"] as? Bool {
      client.isLocateOnce = once
    }
    result(true)
  }

  private func postErrorPayload(_ message: String) {
    let payload: [String: Any] = [
      "type": "error",
      "errorCode": "",
      "errorMessage": message,
    ]
    deliverOrEnqueue(payload)
  }

  private func postLocationPayload(_ payload: [String: Any]) {
    deliverOrEnqueue(payload)
  }

  private func deliverOrEnqueue(_ out: [String: Any]) {
    DispatchQueue.main.async { [weak self] in
      guard let self else { return }
      if let sink = self.eventSink {
        self.flushPendingToFlutter(sink: sink)
        sink(out)
      } else {
        self.pendingLock.lock()
        while self.pendingEvents.count >= maxPendingEvents {
          self.pendingEvents.removeFirst()
        }
        self.pendingEvents.append(out)
        self.pendingLock.unlock()
      }
    }
  }

  private func flushPendingToFlutter(sink: FlutterEventSink? = nil) {
    let sink = sink ?? eventSink
    guard let sink else { return }
    pendingLock.lock()
    let batch = pendingEvents
    pendingEvents.removeAll()
    pendingLock.unlock()
    for item in batch {
      sink(item)
    }
  }

  /// Align keys with Android `WzLocationPlugin.wzLocationToMap`; unknown fields use `NSNull` for a stable shape on Dart.
  private static func locationResToFlutterMap(_ res: LocationRes) -> [String: Any] {
    let detail = res.location
    let lat = detail.position.point.latitude
    let lng = detail.position.point.longitude

    var out: [String: Any] = [:]
    out["type"] = "location"
    out["latitude"] = lat
    out["longitude"] = lng
    out["altitude"] = NSNull()
    out["speed"] = NSNull()
    out["bearing"] = NSNull()
    out["accuracy"] = NSNull()
    out["confidence"] = NSNull()
    out["hasAltitude"] = NSNull()
    out["hasSpeed"] = NSNull()
    out["hasBearing"] = NSNull()
    out["hasAccuracy"] = NSNull()
    out["provider"] = NSNull()
    out["time"] = detail.timestamp
    out["elapsedRealtimeNanos"] = NSNull()
    out["hasVerticalAccuracy"] = NSNull()
    out["verticalAccuracyMeters"] = NSNull()
    out["hasSpeedAccuracy"] = NSNull()
    out["speedAccuracyMetersPerSecond"] = NSNull()
    out["hasBearingAccuracy"] = NSNull()
    out["bearingAccuracyDegrees"] = NSNull()

    out["address"] = NSNull()
    out["addressDescription"] = NSNull()
    out["country"] = NSNull()
    out["countryCode"] = NSNull()
    out["provinceCode"] = NSNull()
    out["province"] = NSNull()
    out["city"] = NSNull()
    out["cityCode"] = NSNull()
    out["district"] = NSNull()
    out["districtCode"] = NSNull()
    out["township"] = NSNull()
    out["street"] = NSNull()
    out["houseNumber"] = NSNull()
    out["postCode"] = NSNull()

    out["id"] = res.id
    out["source"] = res.asset
    out["room"] = NSNull()
    out["floor"] = NSNull()
    out["buildingId"] = NSNull()
    out["buildingName"] = NSNull()
    out["boundingArea"] = NSNull()
    out["scenario"] = NSNull()
    out["place"] = NSNull()
    out["subScene"] = NSNull()
    out["mainScene"] = NSNull()
    out["businessAreas"] = []
    out["nearbyPlaces"] = []
    out["tags"] = []

    if let addr = detail.address {
      out["address"] = addr.name
      applyAddressContext(addr, into: &out)
    }

    if let place = detail.place {
      out["place"] = [
        "id": NSNull(),
        "name": place.name,
        "categoryId": NSNull(),
        "categoryName": NSNull(),
        "type": place.type,
        "distance": place.distance.line,
      ]
    }

    return out
  }

  private static func applyAddressContext(_ address: AddressInfo, into dict: inout [String: Any]) {
    dict["addressDescription"] = address.name
    for item in address.context {
      let typeLower = item.type.lowercased()
      let name = item.name
      let code = item.code
      switch typeLower {
      case "country", "国家":
        dict["country"] = name
        dict["countryCode"] = code
      case "province", "省":
        dict["province"] = name
        dict["provinceCode"] = code
      case "city", "市":
        dict["city"] = name
        dict["cityCode"] = code
      case "district", "区", "县":
        dict["district"] = name
        dict["districtCode"] = code
      case "township", "乡镇":
        dict["township"] = name
      case "street", "道路", "路":
        dict["street"] = name
      case "housenumber", "门牌", "号":
        dict["houseNumber"] = name
      case "postcode", "邮编":
        dict["postCode"] = code.isEmpty ? name : code
      default:
        break
      }
    }
  }

  deinit {
    client.stopLocation()
  }
}

private final class WzLocationDelegateBridge: NSObject, LocationDelegate {
  var onLocation: ((LocationRes) -> Void)?
  var onError: ((String) -> Void)?

  func onReceivedLocation(wzLocation: LocationRes) {
    onLocation?(wzLocation)
  }

  func onLocationError(msg: String) {
    onError?(msg)
  }

  func onReceivedGeocode(result: [GeocodeRes]) {}

  func onReceivedPoiSearch(result: [GeocodeRes]) {}
}

private final class WzLocationStreamHandler: NSObject, FlutterStreamHandler {
  weak var plugin: WzLocationPlugin?

  init(plugin: WzLocationPlugin) {
    self.plugin = plugin
    super.init()
  }

  func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
    plugin?.attachSink(events)
    return nil
  }

  func onCancel(withArguments arguments: Any?) -> FlutterError? {
    plugin?.cancelSink()
    return nil
  }
}
