//
//  AmplitudeEngagementAdapter.swift
//  PluginEngagementReactNative
//
//  Created by Jared Luxenberg on 5/19/25.
//

import AmplitudeEngagementSwift
import Foundation
import os

extension Encodable {
  /// Converting object to postable dictionary
  func toDictionary(_ encoder: JSONEncoder = JSONEncoder()) throws -> [String: Any] {
    let data = try encoder.encode(self)
    let object = try JSONSerialization.jsonObject(with: data)
    if let json = object as? [String: Any] { return json }

    let context = DecodingError.Context(
      codingPath: [], debugDescription: "Deserialized object is not a dictionary")
    throw DecodingError.typeMismatch(type(of: object), context)
  }
}

// id -> (api_key, amplitude engagement instance)
var instances: [Int: (String, AmplitudeEngagement)] = [:]
var _id = 0

/// `@objc` attribute exposes Swift methods to the Objective-C runtime*
@objc public class AmplitudeEngagementAdapter: NSObject {
  private static let logger = Logger(
    subsystem: "PluginEngagementReactNative",
    category: "AmplitudeEngagementAdapter")

  private func getInstanceOrNoOpStub(_ instanceId: Int) -> AmplitudeEngagement {
    guard let (_, instance) = instances[instanceId] else {
      AmplitudeEngagementAdapter.logger.error(
        "No AmplitudeEngagement instance found for id: \(instanceId). Returning no-op stub.")
      return AmplitudeEngagementNoOpStub(
        "", AmplitudeInitOptions(), DevelopmentPlatform.reactNative(version: ""))
    }

    return instance
  }

  @objc public func newInstance(_ apiKey: String, _ options: [String: Any]?) -> Int {
    if let existingId = instances.first(where: { $0.value.0 == apiKey })?.key {
      return existingId
    }

    var logLevel: AmplitudeLogLevel = .verbose
    if let logLevelStr: String = options?["logLevel"] as? String {
      switch logLevelStr.lowercased() {
      case "none":
        logLevel = .none
      case "error":
        logLevel = .error
      case "warn":
        logLevel = .warn
      case "verbose", "info":
        logLevel = .verbose
      case "debug":
        logLevel = .debug
      default:
        AmplitudeEngagementAdapter.logger.log(
          "Unknown logLevel: \(logLevelStr). Defaulting to 'verbose'.")
        logLevel = .verbose
      }
    }

    var serverZone = AmplitudeServerZone.US
    if let serverZoneStr: String = options?["serverZone"] as? String {
      switch serverZoneStr.lowercased() {
      case "us":
        serverZone = .US
      case "eu":
        serverZone = .EU
      default:
        serverZone = .US
      }
    }

    let locale = options?["locale"] as? String
    let ignoreAnalyticsAutomaticScreenTracking =
      options?["ignoreAnalyticsAutomaticScreenTracking"] as? Bool

    let serverUrl = options?["serverUrl"] as? String
    let cdnUrl = options?["cdnUrl"] as? String
    let mediaUrl = options?["mediaUrl"] as? String
    let autoRefreshInterval = options?["autoRefreshInterval"] as? Int

    let initOptions = AmplitudeInitOptions(
      serverZone: serverZone,
      locale: locale,
      logLevel: logLevel,
      serverUrl: serverUrl,
      cdnUrl: cdnUrl,
      mediaUrl: mediaUrl,
      ignoreAnalyticsAutomaticScreenTracking: ignoreAnalyticsAutomaticScreenTracking,
      autoRefreshInterval: autoRefreshInterval
    )

    AmplitudeEngagementAdapter.logger.debug(
      "AmplitudeEngagementAdapter#newInstace apiKey=\(apiKey), options=\(String(describing: options), privacy: .public); initOptions=\(String(describing: initOptions), privacy: .public)"
    )

    let platformVersion = options?["platformVersion"] as? String ?? ""

    return DispatchQueue.main.sync {
      _id += 1
      let id = _id + 1
      AmplitudeEngagementAdapter.logger.debug(
        "AmplitudeEngagementAdapter#newInstace id=\(id)"
      )
      instances[id] = (
        apiKey,
        __ReactNative__AESDK_Factory.make(apiKey, initOptions, platformVersion: platformVersion)
      )
      return id
    }
  }

  @objc public func boot(
    _ instanceId: Int, _ user_id: String?, device_id: String? = nil,
    user_properties: [String: Any]? = nil,
    integration: @escaping (_ event_type: String, _ event: [String: Any]) -> Void
  ) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      let options = AmplitudeBootOptions(
        user_id: user_id, device_id: device_id,
        integrations: [
          { event, props in
            integration(event, props)
          }
        ], user_properties: user_properties ?? [:])
      return instance.boot(options: options)
    }
  }

  @objc public func enable(_ instanceId: Int) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      instance.enable()
    }
  }

  @objc public func disable(_ instanceId: Int) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      instance.disable()
    }
  }

  @objc public func shutdown(_ instanceId: Int) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.shutdown()
    }
  }

  @objc public func setThemeMode(_ instanceId: Int, themeMode: String) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      guard let themeModeParsed = ThemeMode(rawValue: themeMode) else { return }

      return instance.setThemeMode(themeMode: themeModeParsed)
    }
  }

  @objc public func reset(_ instanceId: Int, key: String, stepIndex: Int = 0) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.reset(key: key, stepIndex: stepIndex)
    }
  }

  @objc public func list(_ instanceId: Int) -> [[String: Any]] {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.list().compactMap { x in try? x.toDictionary() }
    }
  }

  @objc public func handleUrl(_ instanceId: Int, _ url: String) -> Bool {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      guard let url = URL(string: url) else { return false }

      return instance.handleUrl(url)
    }
  }

  @objc public func show(_ instanceId: Int, key: String, stepIndex: Int = 0) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.show(key: key, stepIndex: stepIndex)
    }
  }

  @objc public func screen(_ instanceId: Int, _ screenName: String) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.screen(screenName)
    }
  }

  @objc public func closeAll(_ instanceId: Int) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.closeAll()
    }
  }

  @objc public func forwardEvent(_ instanceId: Int, _ event: [String: Any]) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.forwardEvent(event)
    }
  }

  @objc(addCallback:::) public func addCallback(
    _ instanceId: Int, _ key: String, _ function: @escaping () -> Void
  ) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      return instance.addCallback(key, function)
    }
  }

  @objc(setRouter::) public func setRouter(
    _ instanceId: Int, _ function: @escaping (String) -> Void
  ) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      instance.setRouter(function)
    }
  }

  @objc public func unsetRouter(_ instanceId: Int) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      instance.unsetRouter()
    }
  }

  @objc public func updateLanguage(_ instanceId: Int, _ locale: String) {
    DispatchQueue.main.sync {
      let instance = getInstanceOrNoOpStub(instanceId)
      instance.updateLanguage(locale)
    }
  }

}
