import AVFoundation
import Combine
import ComposableArchitecture
import Foundation
import MediaPlayer

// Actor to safely handle notification observers
private actor VideoCoverObserver {
    var notificationObserver: NSObjectProtocol?

    func setObserver(_ observer: NSObjectProtocol?) async {
        notificationObserver = observer
    }

    func getObserver() async -> NSObjectProtocol? {
        return notificationObserver
    }

    func clearObserver() async {
        if let observer = notificationObserver {
            NotificationCenter.default.removeObserver(observer)
            notificationObserver = nil
        }
    }
}

public struct VideoCoverClient {
    public var setup: @Sendable () -> Void
    public var replaceCurrentItem: @Sendable (URL) async -> (CMTime)
    public var replaceCurrentPreviewItem: @Sendable (URL) async -> (CMTime)
    public var play: @Sendable () -> Void
    public var pause: @Sendable () -> Void
    public var playPreview: @Sendable () -> Void
    public var pausePreview: @Sendable () -> Void
    public var restart: @Sendable () -> Void
    public var getAVPlayer: @Sendable () -> AVPlayer
    public var isPlaybackLikelyToKeepUp: @Sendable () -> Bool
    public var currentItemUrl: @Sendable () -> String?
    public var currentPreviewItemUrl: @Sendable () -> String?
    public var seek: @Sendable (CMTime) -> Void
    public var cacheVideoCover: @Sendable (URL) -> Void
    public var removeCurrentPreviewItem: @Sendable () -> Void
    public var teardown: @Sendable () -> Void
}

public extension DependencyValues {
    var videoCoverClient: VideoCoverClient {
        get { self[VideoCoverClient.self] }
        set { self[VideoCoverClient.self] = newValue }
    }
}

// Add error enum
enum VideoCoverError: Error {
    case invalidAsset
}

extension VideoCoverClient: DependencyKey {
    public static let liveValue: Self = {
        let avPlayer = AVPlayer()
        var currentlyPlayingUrl: String?
        var currentlyPlayingPreviewUrl: String?
        var isPlaying = false
        var currentTime: CMTime = .zero

        let observerState = VideoCoverObserver()

        actor SetupState {
            var didSetup = false

            func markAsSetup() {
                didSetup = true
            }

            func isSetup() -> Bool {
                return didSetup
            }
        }

        let setupState = SetupState()
        let cacheManager = VideoCoverCacheManager()

        @Sendable
        func cacheAsset(url: URL) {
            let urlString = url.absoluteString
            Task.detached {
                guard await cacheManager.getCachedAsset(urlString) == nil else { return }
                let asset = AVAsset(url: url)
                await cacheManager.setCachedAsset(urlString, asset: asset)
            }
        }

        @Sendable
        func createPlayerItem(for url: URL) async -> AVPlayerItem? {
            let urlString = url.absoluteString

            if let cachedAsset = await cacheManager.getCachedAsset(urlString) {
                do {
                    let duration = try await cachedAsset.load(.duration)
                    guard duration.isValid, duration.seconds > 0, !duration.seconds.isNaN else {
                        return nil
                    }
                    if duration.isValid && duration.seconds > 0 && !duration.seconds.isNaN {
                        let playerItem = AVPlayerItem(asset: cachedAsset)
                        playerItem.preferredForwardBufferDuration = 5.0 // Buffer 5 seconds ahead
                        return playerItem
                    }
                } catch {}
            }

            let asset = AVURLAsset(url: url)
            do {
                // Load the essential properties before creating the player item
                let duration = try await asset.load(.duration)
                guard duration.isValid, duration.seconds > 0, !duration.seconds.isNaN else {
                    return nil
                }
                let playerItem = AVPlayerItem(asset: asset)

                // Set buffer properties to improve playback
                playerItem.automaticallyPreservesTimeOffsetFromLive = false
                playerItem.preferredForwardBufferDuration = 5.0 // Buffer 5 seconds ahead

                cacheAsset(url: url)
                return playerItem
            } catch {
                return nil
            }
        }

        @Sendable
        func updateLoopObserver(for item: AVPlayerItem?) {
            Task.detached { @Sendable in
                await observerState.clearObserver()

                guard let item = item else {
                    return
                }

                let observer = NotificationCenter.default.addObserver(
                    forName: .AVPlayerItemDidPlayToEndTime,
                    object: item,
                    queue: .main
                ) { [weak avPlayer] notification in
                    guard let playerItem = notification.object as? AVPlayerItem,
                          playerItem === item,
                          let player = avPlayer else { return }

                    Task.detached {
                        await player.seek(to: .zero)
                        player.play()
                    }
                }

                await observerState.setObserver(observer)
            }
        }

        return Self(
            setup: {
                Task {
                    let isAlreadySetup = await setupState.isSetup()
                    guard !isAlreadySetup else { return }
                    avPlayer.isMuted = true

                    await setupState.markAsSetup()
                }
            },
            replaceCurrentItem: { newUrl async in
                let urlString = newUrl.absoluteString

                var currentPreviewTime: CMTime?
                if currentlyPlayingPreviewUrl != nil {
                    let time = avPlayer.currentTime()
                    if time.isValid, time.seconds > 0 {
                        currentPreviewTime = time
                    }
                }

                guard let playerItem = await createPlayerItem(for: newUrl) else {
                    // Clear the current URL if we failed to create a player item
                    currentlyPlayingUrl = nil
                    currentlyPlayingPreviewUrl = nil
                    avPlayer.replaceCurrentItem(with: nil)
                    return .zero
                }

                avPlayer.pause()
                isPlaying = false

                // Add a small delay to ensure good buffering before replacement
                try? await Task.sleep(for: .milliseconds(100))

                // Set the URL before replacing the item to ensure proper state tracking
                currentlyPlayingUrl = urlString
                currentlyPlayingPreviewUrl = nil

                avPlayer.replaceCurrentItem(with: playerItem)
                updateLoopObserver(for: playerItem)

                @Shared(.inMemory(.playingVideoCoverUrlKey)) var playingVideoCoverUrl: String = ""
                $playingVideoCoverUrl.withLock {
                    $0 = urlString
                }

                // Wait briefly to let buffering begin
                try? await Task.sleep(for: .milliseconds(200))

                if let startTime = currentPreviewTime {
                    await avPlayer.seek(to: startTime)
                    if isPlaying {
                        if playerItem.isPlaybackLikelyToKeepUp {
                            avPlayer.play()
                            isPlaying = true
                        } else {
                            // Set up an observer for when playback is likely to keep up
                            let observation = playerItem.observe(\.isPlaybackLikelyToKeepUp) { item, _ in
                                if item.isPlaybackLikelyToKeepUp {
                                    avPlayer.play()
                                    isPlaying = true
                                }
                            }

                            // Add a timeout in case buffering takes too long
                            Task.detached {
                                try? await Task.sleep(for: .seconds(1))
                                if !playerItem.isPlaybackLikelyToKeepUp {
                                    avPlayer.play()
                                    isPlaying = true
                                }
                                // Release the observation
                                withExtendedLifetime(observation) {}
                            }
                        }
                    }
                }

                let duration = try? await playerItem.asset.load(.duration)
                return duration ?? .zero
            },
            replaceCurrentPreviewItem: { newUrl async in
                let urlString = newUrl.absoluteString

                var currentFullTime: CMTime?
                if currentlyPlayingUrl != nil {
                    let time = avPlayer.currentTime()
                    if time.isValid, time.seconds > 0 {
                        currentFullTime = time
                    }
                }

                guard let playerItem = await createPlayerItem(for: newUrl) else {
                    return .zero
                }

                avPlayer.pause()
                isPlaying = false

                avPlayer.replaceCurrentItem(with: playerItem)
                updateLoopObserver(for: playerItem)

                if let startTime = currentFullTime {
                    await avPlayer.seek(to: startTime)
                    if isPlaying {
                        avPlayer.play()
                        isPlaying = true
                    }
                }

                currentlyPlayingPreviewUrl = urlString
                currentlyPlayingUrl = nil

                let duration = try? await playerItem.asset.load(.duration)
                return duration ?? .zero
            },
            play: {
                guard let currentItem = avPlayer.currentItem else {
                    return
                }

                if !currentItem.isPlaybackLikelyToKeepUp {
                    // Set up an observer for when playback is likely to keep up
                    let observation = currentItem.observe(\.isPlaybackLikelyToKeepUp) { item, _ in
                        if item.isPlaybackLikelyToKeepUp {
                            avPlayer.play()
                            isPlaying = true
                        }
                    }

                    // Store the observation somewhere if needed, or use a task to auto-dispose after timeout
                    Task.detached {
                        // Add a timeout in case buffering takes too long
                        try? await Task.sleep(for: .seconds(2))
                        if !currentItem.isPlaybackLikelyToKeepUp {
                            avPlayer.play()
                            isPlaying = true
                        }
                        // Release the observation
                        withExtendedLifetime(observation) {}
                    }

                    return
                }

                avPlayer.play()
                isPlaying = true
            },
            pause: {
                avPlayer.pause()
                isPlaying = false
            },
            playPreview: {
                avPlayer.play()
                isPlaying = true
            },
            pausePreview: {
                avPlayer.pause()
                isPlaying = false
            },
            restart: {
                avPlayer.seek(to: .zero)
                if isPlaying {
                    avPlayer.play()
                }
            },
            getAVPlayer: { avPlayer },
            isPlaybackLikelyToKeepUp: {
                avPlayer.currentItem?.isPlaybackLikelyToKeepUp ?? false
            },
            currentItemUrl: {
                currentlyPlayingUrl
            },
            currentPreviewItemUrl: {
                currentlyPlayingPreviewUrl
            },
            seek: { time in
                guard let currentItem = avPlayer.currentItem else { return }

                let duration = currentItem.duration
                if duration.isValid, duration.seconds > 0 {
                    let currentTime = currentItem.currentTime()
                    let normalizedSeconds = time.seconds.truncatingRemainder(dividingBy: duration.seconds)
                    let normalizedTime = CMTime(seconds: normalizedSeconds, preferredTimescale: time.timescale)

                    Task.detached {
                        await avPlayer.seek(to: normalizedTime, toleranceBefore: .zero, toleranceAfter: .zero)
                    }
                }
            },
            cacheVideoCover: { url in
                Task.detached {
                    let urlString = url.absoluteString
                    if await cacheManager.getCachedAsset(urlString) == nil {
                        let asset = AVURLAsset(url: url)

                        // Pre-load duration to validate asset
                        do {
                            let duration = try await asset.load(.duration)
                            if duration.isValid, duration.seconds > 0, !duration.seconds.isNaN {
                                await cacheManager.setCachedAsset(urlString, asset: asset)
                            }
                        } catch {}
                    }
                }
            },
            removeCurrentPreviewItem: {
                avPlayer.replaceCurrentItem(with: nil)
                currentlyPlayingPreviewUrl = nil

                Task.detached { @Sendable in
                    await observerState.clearObserver()
                }
            },
            teardown: {
                Task.detached { @Sendable in
                    await observerState.clearObserver()

                    avPlayer.replaceCurrentItem(with: nil)
                }
            }
        )
    }()
}
