import Foundation
import Combine
import AVFoundation

/// Real audio recorder using AVAudioRecorder for production recording.
/// Requests microphone permissions, configures AVAudioSession, and outputs AAC (.m4a) files.
final class RealAudioRecorder: AudioRecorderProtocol {
    // MARK: - Published Properties
    
    @Published var isRecording: Bool = false
    @Published var elapsedTime: TimeInterval = 0
    @Published var currentAmplitude: Float = 0
    @Published var waveformSamples: [Float] = []
    @Published var outputFileURL: URL? = nil
    @Published var isPaused: Bool = false
    
    // MARK: - Private Properties
    
    private var audioRecorder: AVAudioRecorder?
    private var timer: Timer?
    private let timerInterval: TimeInterval = 0.05
    private let maxSampleCount = 200
    
    init() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleInterruption),
            name: AVAudioSession.interruptionNotification,
            object: AVAudioSession.sharedInstance()
        )
    }
    
    // MARK: - AudioRecorderProtocol
    
    func startRecording() {
        guard !isRecording else { return }
        
        // Reset state
        elapsedTime = 0
        currentAmplitude = 0
        waveformSamples = []
        outputFileURL = nil
        
        // Request Permission
        AVAudioApplication.requestRecordPermission { [weak self] granted in
            guard let self else { return }
            if granted {
                DispatchQueue.main.async {
                    self.setupAndRecord()
                }
            } else {
                print("[RealAudioRecorder] Microphone permission denied")
            }
        }
    }
    
    private func setupAndRecord() {
        let session = AVAudioSession.sharedInstance()
        do {
            // Set up audio session category and mode for recording, ducking other apps
            try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowBluetoothHFP, .duckOthers])
            try session.setActive(true)
            
            // Create target file URL
            let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let filename = "recording_\(UUID().uuidString).m4a"
            let fileURL = documents.appendingPathComponent(filename)
            self.outputFileURL = fileURL
            
            // Configure AAC recorder settings
            let settings: [String: Any] = [
                AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                AVSampleRateKey: 44100.0,
                AVNumberOfChannelsKey: 1,
                AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
            ]
            
            let recorder = try AVAudioRecorder(url: fileURL, settings: settings)
            recorder.isMeteringEnabled = true
            
            if recorder.prepareToRecord() {
                recorder.record()
                self.audioRecorder = recorder
                self.isRecording = true
                self.isPaused = false
                self.startTimer()
                print("[RealAudioRecorder] Recording started: \(fileURL.lastPathComponent)")
            } else {
                print("[RealAudioRecorder] Prepare to record failed")
            }
        } catch {
            print("[RealAudioRecorder] Setup failed: \(error.localizedDescription)")
        }
    }
    
    func stopRecording() {
        guard isRecording else { return }
        
        invalidateTimer()
        audioRecorder?.stop()
        audioRecorder = nil
        isRecording = false
        isPaused = false
        currentAmplitude = 0
        
        // Deactivate audio session
        try? AVAudioSession.sharedInstance().setActive(false)
        print("[RealAudioRecorder] Recording stopped. File saved.")
    }
    
    func pauseRecording() {
        guard isRecording else { return }
        audioRecorder?.pause()
        isPaused = true
        invalidateTimer()
    }
    
    func resumeRecording() {
        guard isRecording else { return }
        try? AVAudioSession.sharedInstance().setActive(true)
        audioRecorder?.record()
        isPaused = false
        startTimer()
    }
    
    // MARK: - Private Helpers
    
    private func startTimer() {
        invalidateTimer()
        timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true) { [weak self] _ in
            self?.tick()
        }
        if let timer {
            RunLoop.current.add(timer, forMode: .common)
        }
    }
    
    private func invalidateTimer() {
        timer?.invalidate()
        timer = nil
    }
    
    private func tick() {
        guard let recorder = audioRecorder, recorder.isRecording else { return }
        
        // Update metering
        recorder.updateMeters()
        elapsedTime = recorder.currentTime
        
        // Get average decibels for channel 0
        let power = recorder.averagePower(forChannel: 0)
        
        // Keep live and playback waveform levels on the same scale.
        let level = AudioLevelNormalizer.normalizedLevel(decibels: power)
        
        currentAmplitude = level
        waveformSamples.append(level)
        
        if waveformSamples.count > maxSampleCount {
            waveformSamples.removeFirst(waveformSamples.count - maxSampleCount)
        }
    }
    
    @objc private func handleInterruption(notification: Notification) {
        guard let userInfo = notification.userInfo,
              let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
              let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
            return
        }
        
        switch type {
        case .began:
            DispatchQueue.main.async { [weak self] in
                guard let self else { return }
                if self.isRecording && !self.isPaused {
                    self.pauseRecording()
                    print("[RealAudioRecorder] Audio session interrupted. Recording automatically paused.")
                }
            }
        case .ended:
            guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
            let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
            if options.contains(.shouldResume) {
                DispatchQueue.main.async { [weak self] in
                    guard let self else { return }
                    if self.isRecording && self.isPaused {
                        self.resumeRecording()
                        print("[RealAudioRecorder] Interruption ended. Recording automatically resumed.")
                    }
                }
            }
        @unknown default:
            break
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
        invalidateTimer()
        audioRecorder?.stop()
    }
}

// MARK: - AudioMerger

/// Utility struct to merge existing audio recording file with newly recorded audio segment.
struct AudioMerger {
    /// Merges an existing audio file and a newly recorded audio segment into a single unified .m4a file.
    /// Uses modern async/await AVFoundation APIs to ensure tracks and durations are fully loaded
    /// before building the composition, preventing empty-track or indefinite-duration bugs.
    /// - Parameters:
    ///   - firstURL: The original audio file URL (can be nil or non-existent).
    ///   - secondURL: The newly recorded audio segment URL.
    /// - Returns: The resulting merged (or fallback) file URL.
    @MainActor
    static func mergeAudioFiles(firstURL: URL?, secondURL: URL) async -> URL {
        let resolvedFirst = AudioPathHelper.resolveURL(for: firstURL?.path)
        let resolvedSecond = AudioPathHelper.resolveURL(for: secondURL.path) ?? secondURL
        
        guard let validFirstURL = resolvedFirst else {
            return resolvedSecond
        }
        
        guard FileManager.default.fileExists(atPath: resolvedSecond.path) else {
            return validFirstURL
        }
        
        let composition = AVMutableComposition()
        guard let compositionTrack = composition.addMutableTrack(
            withMediaType: .audio,
            preferredTrackID: kCMPersistentTrackID_Invalid
        ) else {
            print("[AudioMerger] Failed to create composition audio track")
            return resolvedSecond
        }
        
        let asset1 = AVURLAsset(url: validFirstURL)
        let asset2 = AVURLAsset(url: resolvedSecond)
        
        do {
            // Load durations and tracks asynchronously to ensure they are ready
            let duration1 = try await asset1.load(.duration)
            let duration2 = try await asset2.load(.duration)
            let tracks1 = try await asset1.loadTracks(withMediaType: .audio)
            let tracks2 = try await asset2.loadTracks(withMediaType: .audio)
            
            if let track1 = tracks1.first {
                try compositionTrack.insertTimeRange(
                    CMTimeRange(start: .zero, duration: duration1),
                    of: track1,
                    at: .zero
                )
                print("[AudioMerger] Inserted first track: \(CMTimeGetSeconds(duration1))s")
            } else {
                print("[AudioMerger] Warning: No audio track found in first asset")
            }
            
            // Insert second track right after the first
            let insertionPoint = compositionTrack.timeRange.duration
            if let track2 = tracks2.first {
                try compositionTrack.insertTimeRange(
                    CMTimeRange(start: .zero, duration: duration2),
                    of: track2,
                    at: insertionPoint
                )
                print("[AudioMerger] Inserted second track: \(CMTimeGetSeconds(duration2))s at offset \(CMTimeGetSeconds(insertionPoint))s")
            } else {
                print("[AudioMerger] Warning: No audio track found in second asset")
            }
        } catch {
            print("[AudioMerger] Error loading/inserting tracks: \(error.localizedDescription)")
            return resolvedSecond
        }
        
        // Export merged composition
        let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let mergedURL = documents.appendingPathComponent("merged_\(UUID().uuidString).m4a")
        
        guard let exportSession = AVAssetExportSession(
            asset: composition,
            presetName: AVAssetExportPresetAppleM4A
        ) else {
            print("[AudioMerger] Failed to create AVAssetExportSession")
            return resolvedSecond
        }
        
        exportSession.outputURL = mergedURL
        exportSession.outputFileType = .m4a
        
        await exportSession.export()
        
        switch exportSession.status {
        case .completed:
            let mergedDuration = try? await AVURLAsset(url: mergedURL).load(.duration)
            let mergedSeconds = mergedDuration.map(CMTimeGetSeconds) ?? 0
            print("[AudioMerger] Successfully merged audio to: \(mergedURL.lastPathComponent) (\(mergedSeconds)s)")
            return mergedURL
        default:
            print("[AudioMerger] Audio export failed: \(String(describing: exportSession.error))")
            return resolvedSecond
        }
    }
}
