//
//  SoundPlayer.swift
//  AudioPlaybackKit
//
//  Created by Matt Bruce on 9/8/25.
//

import AVFoundation
import Observation

/// Audio playback service for sounds and ambient audio
@available(iOS 17.0, tvOS 17.0, *)
@Observable
public class SoundPlayer {
    
    // MARK: - Singleton
    public static let shared = SoundPlayer()
    
    // MARK: - Properties
    private var players: [String: AVAudioPlayer] = [:]
    private var currentPlayer: AVAudioPlayer?
    private var currentSound: Sound?
    private var shouldResumeAfterInterruption = false
    private let wakeLockService = WakeLockService.shared
    private let soundConfigurationService = SoundConfigurationService.shared
    
    // MARK: - Initialization
    private init() {
        setupAudioSession()
        preloadSounds()
        setupAudioInterruptionHandling()
    }
    
    deinit {
        stopAllSounds()
    }
    
    // MARK: - Public Interface
    public var isPlaying: Bool {
        return currentPlayer?.isPlaying ?? false
    }
    
    public func playSound(_ sound: Sound) {
        print("🎵 Attempting to play: \(sound.name)")
        
        // Stop current sound if playing
        stopSound()
        
        // Store current sound for interruption handling
        currentSound = sound
        
        // Get or create player for this sound
        guard let player = players[sound.fileName] else {
            print("❌ Sound not preloaded: \(sound.fileName)")
            print("📁 Available sounds: \(players.keys)")
            
            // Try to load the sound dynamically as fallback
            guard let fileUrl = getURL(for: sound) else {
                print("❌ Sound file not found: \(sound.fileName)")
                return
            }
            
            do {
                let newPlayer = try AVAudioPlayer(contentsOf: fileUrl)
                newPlayer.numberOfLoops = AudioConstants.Playback.numberOfLoops
                newPlayer.volume = AudioConstants.Volume.default
                newPlayer.prepareToPlay()
                players[sound.fileName] = newPlayer
                currentPlayer = newPlayer
                let success = newPlayer.play()
                print("🎵 Fallback play result: \(success ? "SUCCESS" : "FAILED")")
                return
            } catch {
                print("❌ Error creating fallback player: \(error)")
                return
            }
        }
        
        currentPlayer = player
        let success = player.play()
        print("🎵 Play result: \(success ? "SUCCESS" : "FAILED")")
        print("🔊 Player isPlaying: \(player.isPlaying)")
        print("🔊 Player volume: \(player.volume)")
        
        // Enable wake lock when playing audio to prevent device sleep
        if success {
            wakeLockService.enableWakeLock()
        }
    }
    
    public func stopSound() {
        currentPlayer?.stop()
        currentPlayer = nil
        currentSound = nil
        shouldResumeAfterInterruption = false
        
        // Disable wake lock when stopping audio
        wakeLockService.disableWakeLock()
    }
    
    // MARK: - Private Methods
    
    /// Helper method to get URL for sound file, handling bundles and direct paths
    private func getURL(for sound: Sound) -> URL? {
        // If sound has a bundle name, look in that bundle first
        if let bundleName = sound.bundleName {
            if let bundleURL = Bundle.main.url(forResource: bundleName, withExtension: "bundle"),
               let bundle = Bundle(url: bundleURL) {
                return bundle.url(forResource: sound.fileName, withExtension: nil)
            }
        }
        
        // Fallback to direct file path
        if sound.fileName.contains("/") {
            // Path includes subfolder (e.g., "Sounds/white-noise.mp3")
            let components = sound.fileName.components(separatedBy: "/")
            let fileName = components.last!
            let subfolder = components.dropLast().joined(separator: "/")
            return Bundle.main.url(forResource: fileName, withExtension: nil, subdirectory: subfolder)
        } else {
            // Direct file path (fallback)
            return Bundle.main.url(forResource: sound.fileName, withExtension: nil)
        }
    }
    
    private func setupAudioSession() {
        do {
            let settings = soundConfigurationService.getAudioSettings()
            
            // Use configuration settings or fall back to constants
            let category = settings.audioSessionCategory == "playback" ? 
                AVAudioSession.Category.playback : AudioConstants.AudioSession.category
            let mode = settings.audioSessionMode == "default" ? 
                AVAudioSession.Mode.default : AudioConstants.AudioSession.mode
            let options: AVAudioSession.CategoryOptions = settings.audioSessionOptions.contains("mixWithOthers") == true ? 
                [.mixWithOthers] : AudioConstants.AudioSession.options
            
            try AVAudioSession.sharedInstance().setCategory(category, mode: mode, options: options)
            try AVAudioSession.sharedInstance().setActive(true)
            
            // Configure for background audio playback
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
            try AVAudioSession.sharedInstance().setActive(true)
            
            print("🔊 Audio session configured for background playback")
        } catch {
            print("Error setting up audio session: \(error)")
        }
    }
    
    private func preloadSounds() {
        print("📁 Preloading audio files...")
        
        // Get sound configuration
        let sounds = soundConfigurationService.getAvailableSounds()
        let settings = soundConfigurationService.getAudioSettings()
        
        for sound in sounds {
            guard let fileUrl = getURL(for: sound) else {
                print("❌ Sound file not found: \(sound.fileName)")
                continue
            }
            
            do {
                let player = try AVAudioPlayer(contentsOf: fileUrl)
                player.numberOfLoops = settings.defaultLoopCount
                player.volume = settings.defaultVolume
                if settings.preloadSounds {
                    player.prepareToPlay()
                }
                players[sound.fileName] = player
                print("✅ Loaded: \(sound.name) (\(sound.fileName))")
            } catch {
                print("❌ Error preloading sound \(sound.fileName): \(error)")
            }
        }
        print("📁 Preloading complete. Loaded \(players.count) sounds.")
    }
    
    private func stopAllSounds() {
        for player in players.values {
            player.stop()
        }
        players.removeAll()
        currentPlayer = nil
        currentSound = nil
        shouldResumeAfterInterruption = false
    }
    
    /// Set up audio interruption handling to maintain playback
    private func setupAudioInterruptionHandling() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleAudioInterruption),
            name: AVAudioSession.interruptionNotification,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleRouteChange),
            name: AVAudioSession.routeChangeNotification,
            object: nil
        )
    }
    
    @objc private func handleAudioInterruption(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:
            // Audio was interrupted (e.g., phone call)
            shouldResumeAfterInterruption = isPlaying
            if isPlaying {
                currentPlayer?.pause()
                print("🔇 Audio interrupted - will resume after interruption ends")
            }
            
        case .ended:
            // Audio interruption ended
            if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
                let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
                if options.contains(.shouldResume) && shouldResumeAfterInterruption {
                    // Resume playback
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        self.resumePlayback()
                    }
                }
            }
            
        @unknown default:
            break
        }
    }
    
    @objc private func handleRouteChange(notification: Notification) {
        guard let userInfo = notification.userInfo,
              let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
              let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
            return
        }
        
        switch reason {
        case .oldDeviceUnavailable:
            // Headphones were unplugged, etc.
            if isPlaying {
                shouldResumeAfterInterruption = true
                currentPlayer?.pause()
                print("🔇 Audio route changed - will resume when new route available")
            }
            
        case .newDeviceAvailable:
            // New audio device available
            if shouldResumeAfterInterruption {
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    self.resumePlayback()
                }
            }
            
        default:
            break
        }
    }
    
    /// Resume playback after interruption
    private func resumePlayback() {
        guard shouldResumeAfterInterruption, let sound = currentSound else { return }
        
        do {
            try AVAudioSession.sharedInstance().setActive(true)
            playSound(sound)
            shouldResumeAfterInterruption = false
            print("🔊 Audio playback resumed after interruption")
        } catch {
            print("❌ Error resuming audio playback: \(error)")
        }
    }
}
