import AVFoundation
import Foundation
import React

private func audioRecorderLog(_ message: String) {
  print("[AudioRecorder] \(message)")
}

let RN_EVENT_NAME = "audioRecordingUpdate"

@objc(AudioRecorderModule)
class AudioRecorderModule: RCTEventEmitter, AVAudioRecorderDelegate {
  private var recorder: AVAudioRecorder?
  private var recordingURL: URL?

  private var session: AVAudioSession
  private var directory: URL
  private var dispatchTimer: DispatchSourceTimer?

  override init() {
    directory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
    session = AVAudioSession.sharedInstance()
    super.init()
    audioRecorderLog("init")
  }

  deinit {
    audioRecorderLog("deinit")
    cleanup()
  }

  @objc
  func record(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
    let callActive = CallManager.shared.isAudioSessionOwnedByCall()
    audioRecorderLog("record: callActive=\(callActive)")

    cleanup()

    let settings: [String: Any] = [
      AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
      AVSampleRateKey: 44100,
      AVNumberOfChannelsKey: 2,
      AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
    ]

    let filename = "\(UUID().uuidString).aac"
    let recURL = directory.appendingPathComponent(filename)
    recordingURL = recURL

    do {
      try prepareSession()

      recorder = try AVAudioRecorder(url: recURL, settings: settings)
      recorder?.isMeteringEnabled = true

      guard let recorder = recorder else {
        audioRecorderLog("record ERROR: failed to create recorder")
        reject("recorder_error", "Failed to create recorder", nil)
        return
      }

      guard recorder.prepareToRecord() else {
        audioRecorderLog("record ERROR: failed to prepare recorder")
        reject("prepare_error", "Failed to prepare recorder", nil)
        return
      }

      startTracker()

      guard recorder.record() else {
        stopTracker()
        audioRecorderLog("record ERROR: recorder.record() returned false")
        reject("record_error", "Failed to start recording - recorder.record() returned false", nil)
        return
      }

      audioRecorderLog("record: started successfully, path=\(recURL.absoluteString)")
      resolve(recordingURL?.absoluteString)
    } catch {
      audioRecorderLog("record ERROR: \(error.localizedDescription)")
      reject("start_error", "Failed to start recording", error)
    }
  }

  @objc
  func pause() {
    audioRecorderLog("pause: isRecording=\(recorder?.isRecording ?? false)")
    stopTracker()

    guard recorder?.isRecording == true else {
      return
    }

    recorder?.pause()
  }

  @objc
  func resume() {
    let callActive = CallManager.shared.isAudioSessionOwnedByCall()
    audioRecorderLog("resume: callActive=\(callActive)")
    try? prepareSession()
    startTracker()
    recorder?.record()
  }

  @objc
  func stop(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
    let duration = getDurationHandler()
    audioRecorderLog("stop: duration=\(duration), hadActiveRecorder=\(recorder != nil)")

    recorder?.stop()
    recorder = nil
    stopTracker()

    let res: [String: Any] = [
      "path": recordingURL?.absoluteString ?? "",
      "duration": duration
    ]

    resolve(res)
  }

  @objc
  func getDuration(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
    resolve(getDurationHandler())
  }

  private static func normalizePower(_ power: Float) -> Float {
    let minDb: Float = -60
    let clamped = max(power, minDb)
    return (clamped - minDb) / (0 - minDb)
  }

  private func startTracker() {
    stopTracker()

    let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
    timer.schedule(deadline: .now(), repeating: 0.05)
    timer.setEventHandler { [weak self] in
      guard let self = self, let recorder = self.recorder, recorder.isRecording else { return }
      recorder.updateMeters()
      let power = recorder.averagePower(forChannel: 0)
      let normalizedPower = Self.normalizePower(power)
      self.sendEvent(withName: RN_EVENT_NAME, body: [
        "duration": Int(recorder.currentTime * 1000),
        "metering": normalizedPower
      ])
    }
    timer.resume()
    dispatchTimer = timer
  }

  private func stopTracker() {
    dispatchTimer?.cancel()
    dispatchTimer = nil
  }

  private func getDurationHandler() -> Int {
    if let recorder = recorder {
      return Int(recorder.currentTime * 1000)
    }

    guard let url = recordingURL else {
      return 0
    }

    let asset = AVURLAsset(url: url)
    let durationInMillis = CMTimeGetSeconds(asset.duration) * 1000

    if durationInMillis.isNaN {
      return 0
    } else {
      return Int(durationInMillis)
    }
  }

  private func prepareSession() throws {
    let callActive = CallManager.shared.isAudioSessionOwnedByCall()
    audioRecorderLog("prepareSession: callActive=\(callActive), currentCategory=\(session.category), currentMode=\(session.mode)")

    if callActive {
      audioRecorderLog("prepareSession: SKIPPED — call is active, not modifying audio session")
      return
    }

    try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
    try session.setActive(true, options: .notifyOthersOnDeactivation)
    audioRecorderLog("prepareSession: audio session configured — category=\(session.category), mode=\(session.mode)")
  }

  private func cleanup() {
    audioRecorderLog("cleanup")
    recorder?.stop()
    recorder = nil
    recordingURL = nil
    stopTracker()
  }

  override func supportedEvents() -> [String]! {
    return [RN_EVENT_NAME]
  }
}
