//
//  RNCallManager.swift
//  humand
//
//  Created by Oleksandr Shumihin on 16/5/25.
//  Copyright © 2025 Humand. All rights reserved.
//

import Foundation
import React
import StreamVideo

@objc(RNCallManager)
class RNCallManager:RCTEventEmitter {
  static var shared: RNCallManager?
  static var isReady: Bool = false
  static var savedData: [AnyHashable:Any]?
  static var savedDataTimestamp: Date?
  static var pendingObserverEvents: [(String, [String: Any])] = []
  private static let pendingObserverEventsQueue = DispatchQueue(label: "humand.rncallmanager.pendingObserverEvents")
  private var nativeObserverToken: UUID?

  @objc static func configureService() {
    guard
      let apiUrl = Keys.public(for: "API_URL"),
      let keychainKey = Keys.secure(for: "KEYCHAIN_KEY"),
      let streamApiKey = Keys.secure(for: "STREAMIO_API_KEY")
    else {
      callLog("RNCallManager.configureService: missing Keys entries")
      return
    }

    CallApiService.shared.configure(apiUrl: apiUrl, keychanKey: keychainKey, streamApiKey: streamApiKey)
    _ = CallManager.shared
    callLog("RNCallManager.configureService: Call service configured")
  }

  /// Maximum age for saved data before it's considered stale and cleared (30 seconds)
  private static let savedDataMaxAge: TimeInterval = 30.0

  override init() {
    super.init()
    RNCallManager.shared = self
    nativeObserverToken = NativeCallsEventObserver.shared.addObserver { eventName, properties in
      RNCallManager.emitObservedEvent(eventName, properties: properties)
    }
  }

  deinit {
    if let token = nativeObserverToken {
      NativeCallsEventObserver.shared.removeObserver(token)
    }
  }

  override static func requiresMainQueueSetup() -> Bool { true }

  override func supportedEvents() -> [String] {
    return [
      CallConstants.RNEvents.callAccept,
      CallConstants.RNEvents.callMute,
      CallConstants.RNEvents.callEnd,
      CallConstants.RNEvents.callAudioActivated,
      CallConstants.RNEvents.callAudioRouteChanged,
      CallConstants.RNEvents.callStateChanged,
      CallConstants.RNEvents.callParticipantsChanged,
      CallConstants.RNEvents.callMediaStateChanged,
      CallConstants.RNEvents.callPipModeChanged,
      CallConstants.RNEvents.callObserverStarted,
      CallConstants.RNEvents.callObserverAccepted,
      CallConstants.RNEvents.callObserverRejected,
      CallConstants.RNEvents.callObserverParticipantJoined,
      CallConstants.RNEvents.callObserverEnded,
      CallConstants.RNEvents.callObserverRinging
    ]
  }

  @Injected(\.callKitPushNotificationAdapter) private var callKitPushNotificationAdapter

  @objc func getVoipToken(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
    let token = callKitPushNotificationAdapter.deviceToken
    resolve(token.isEmpty ? nil : token)
  }

  @objc func setSystemMicrophoneEnabled(_ enabled: Bool) {
  }

  @objc func setPipEnabled(_ enabled: Bool) {
    // No-op on iOS: PiP mode is handled by the system, this flag is Android-only.
  }

  @objc func setListenersReady() {
    RNCallManager.isReady = true

    // Check if we have saved data and it's not stale
    if let data = RNCallManager.savedData,
       let timestamp = RNCallManager.savedDataTimestamp {
      let age = Date().timeIntervalSince(timestamp)
      if age <= RNCallManager.savedDataMaxAge {
        RNCallManager.emitAcceptedCall(data)
      } else {
        callLog("Saved call data expired (age: \(age)s), discarding")
      }
      RNCallManager.savedData = nil
      RNCallManager.savedDataTimestamp = nil
    }

    let eventsToEmit = RNCallManager.pendingObserverEventsQueue.sync { () -> [(String, [String: Any])] in
      let snapshot = RNCallManager.pendingObserverEvents
      RNCallManager.pendingObserverEvents.removeAll()
      return snapshot
    }
    eventsToEmit.forEach { eventName, properties in
      RNCallManager.shared?.sendEvent(withName: eventName, body: properties)
    }
  }

  @objc func setAuthTokens(_ accessToken: String, refreshToken: String) {
    CallApiService.shared.setAuthTokens(accessToken: accessToken, refreshToken: refreshToken)
    CallManager.shared.initializeCallKit()
  }

  @objc func setMicrophoneEnabled(_ enabled: Bool) {
    Task { await CallManager.shared.setMicrophoneEnabled(enabled) }
  }

  @objc func setCameraEnabled(_ enabled: Bool) {
    Task { await CallManager.shared.setCameraEnabled(enabled) }
  }

  @objc func setSpeakerEnabled(_ enabled: Bool) {
    Task { await CallManager.shared.setSpeakerEnabled(enabled) }
  }

  @objc func switchCamera() {
    Task { await CallManager.shared.switchCamera() }
  }

  @objc func leaveCall() {
    Task { @MainActor in CallManager.shared.leaveCall() }
  }

  /// Cleanup call state on logout/account switch.
  /// Disconnects the Stream client and clears any active call state.
  @objc func cleanup() {
    CallManager.shared.cleanupCallKit()
  }

  /// Update the isGroup flag for the current call.
  /// Called when participants are added to a 1-to-1 call, making it a group call.
  @objc func setIsGroup(_ isGroup: Bool) {
    CallManager.shared.setIsGroup(isGroup)
  }

  @objc func startCall(_ id: String, providerCallId: String, cameraEnabled: Bool, callerName: String, isGroup: Bool, isCaller: Bool, participantUserIds: [NSNumber]) {
    let userIds = participantUserIds.map { $0.intValue }
    CallManager.shared.startCall(id: id, providerCallId: providerCallId, cameraEnabled: cameraEnabled, callerName: callerName, isGroup: isGroup, isCaller: isCaller, participantUserIds: userIds)
  }

  @objc func getCallState(_ resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
    Task { @MainActor in
      resolve(CallManager.shared.getCurrentState())
    }
  }

  @objc func getCurrentAudioRoute(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
    resolve(CallManager.shared.getCurrentAudioRoute())
  }

  static func emitAcceptedCall(_ data:[AnyHashable:Any]) {
    guard RNCallManager.isReady else {
      RNCallManager.savedData = data
      RNCallManager.savedDataTimestamp = Date()
      return
    }

    shared?.sendEvent(withName: CallConstants.RNEvents.callAccept, body: data)
  }

  static func emitEndCall(){
    shared?.sendEvent(withName: CallConstants.RNEvents.callEnd, body: [:])
  }

  static func emitCallStateChanged(_ state: String) {
    shared?.sendEvent(withName: CallConstants.RNEvents.callStateChanged, body: ["state": state])
  }

  static func emitParticipantsChanged(_ participants: [[String: Any]]) {
    shared?.sendEvent(withName: CallConstants.RNEvents.callParticipantsChanged, body: ["participants": participants])
  }

  static func emitMediaStateChanged(microphoneEnabled: Bool?, cameraEnabled: Bool?, speakerEnabled: Bool?) {
    var body: [String: Any] = [:]
    if let mic = microphoneEnabled { body["microphoneEnabled"] = mic }
    if let cam = cameraEnabled { body["cameraEnabled"] = cam }
    if let speaker = speakerEnabled { body["speakerEnabled"] = speaker }
    shared?.sendEvent(withName: CallConstants.RNEvents.callMediaStateChanged, body: body)
  }

  static func emitAudioRouteChanged(_ audioRoute: String, hasBluetoothDevice: Bool) {
    shared?.sendEvent(
      withName: CallConstants.RNEvents.callAudioRouteChanged,
      body: ["audioRoute": audioRoute, "hasBluetoothDevice": hasBluetoothDevice]
    )
  }

  static func emitObservedEvent(_ eventName: String, properties: [String: Any]) {
    let shouldQueue = RNCallManager.pendingObserverEventsQueue.sync { () -> Bool in
      guard !RNCallManager.isReady else { return false }
      RNCallManager.pendingObserverEvents.append((eventName, properties))
      return true
    }
    if shouldQueue { return }

    shared?.sendEvent(withName: eventName, body: properties)
  }
}
