//
//  PlayerModule.swift
//  Pods
//
//  Created by Asti Manuka on 11/10/23.
//

import AVKit
import CoreMedia
import Foundation
import PRESTOplay

// MARK: - PlayerModule

@available(tvOS 14.0, *)
@objc(PlayerModule)
class PlayerModule: RCTEventEmitter {
    
    public static var requestIdTag: String = "playerId";

    // MARK: Lifecycle

    override init() {
        super.init()
    }
    
    override var bridge: RCTBridge! {
        didSet {
            // Called by React Native AFTER init when using bridge architecture
            if bridge != nil {
                let emitter = PlayerEventEmitter(self)
                setEventEmitter(eventEmitter: emitter)
            }
        }
    }
    
    /**
        Must be called manually when using new architecture
     */
    public func setEventEmitter (eventEmitter: PlayerEventEmitter) {
        
        guard self.playerEventEmitter == nil else {
            return
        }
        
        self.playerEventEmitter = eventEmitter
        // Should be removed on destroy??
        let keyModifier = DeferredKeyModifier(
            eventEmitter: playerEventEmitter,
            requestIdTag: PlayerModule.requestIdTag
        )
        let requestModifier = DeferredRequestModifier(
            eventEmitter: playerEventEmitter,
            requestIdTag: PlayerModule.requestIdTag
        )
        let responseModifier = DeferredResponseModifier(
            eventEmitter: playerEventEmitter,
            requestIdTag: PlayerModule.requestIdTag
        )
        
        //PRESTOplaySDK.shared.requestModifiers = []
        //PRESTOplaySDK.shared.responseModifiers = []

        PRESTOplaySDK.shared.requestModifiers.append(keyModifier)
        PRESTOplaySDK.shared.requestModifiers.append(requestModifier)
        PRESTOplaySDK.shared.responseModifiers.append(responseModifier)

        PRESTOplaySDK.shared.onError = { component, error in
            for instanceId in Repository.shared.players.keySet() {
                let player = Repository.shared.players.get(key: instanceId)
                if player?.sessionId() == component?.sessionId {
                    self.playerEventEmitter.emitError(playerId: player!.id, error: error)
                }
            }
        }
    }

    // MARK: Internal

    var playerEventEmitter: PlayerEventEmitter!

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

    override func stopObserving() {
        playerEventEmitter.stopObserving()
    }

    override func startObserving() {
        playerEventEmitter.startObserving()
    }

    override func supportedEvents() -> [String] {
        playerEventEmitter.supportedEvents()
    }

    @objc
    func create(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter _: @escaping RCTPromiseRejectBlock
    ) {
        Repository.shared.players.add(
            key: instanceId,
            value: PlayerProxy(
                playerId: instanceId,
                playerEventEmitter: playerEventEmitter
            )
        )

        resolve(nil)
    }

    @objc
    func createApplePlayer(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter _: @escaping RCTPromiseRejectBlock
    ) {
        // TODO: check if we can expose the default contructor
        // Here we create the view controller without any configuration
        // The configurations are passed in the open() method call
        Repository.shared.players.add(
            key: instanceId,
            value: ApplePlayerProxy(
                playerId: instanceId,
                playerEventEmitter: playerEventEmitter
            )
        )

        resolve(nil)
    }

    @objc
    func destroy(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.remove(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.destroy()
            resolve(nil)
        }
    }

    @objc
    func open(
        _ instanceId: String!,
        playerConfiguration jsonPlayerConfiguration: [String: Any],
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        do {
            try player.open(
                jsonPlayerConfiguration: jsonPlayerConfiguration,
                completion: {
                    resolve(nil)
                }
            )
        } catch let error as PrestoPlayError {
            Rejecter(reject).reject(error)
        } catch {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .invalidConfiguration))
        }
    }

    @objc
    func play(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.play()
            resolve(nil)
        }
    }

    @objc
    func replay(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.replay()
            resolve(nil)
        }
    }

    @objc
    func pause(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.pause()
            resolve(nil)
        }
    }

    @objc
    func stop(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.stop()
            resolve(nil)
        }
    }

    @objc
    func setPosition(
        _ instanceId: String!,
        newPositionMs: Int64,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.setPosition(newPositionMs)
            resolve(nil)
        }
    }

    @objc
    func setPlaybackRate(
        _ instanceId: String!,
        newPlaybackRate: Double,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }

        DispatchQueue.main.async {
            player.setPlaybackRate(newPlaybackRate)
            resolve(nil)
        }
    }

    @objc
    func setVolume(
        _ instanceId: String!,
        newVolume: Double,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }

        DispatchQueue.main.async {
            player.setVolume(newVolume)
            resolve(nil)
        }
    }

    @objc
    func setMuted(
        _ instanceId: String!,
        newMuted: Bool,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }

        DispatchQueue.main.async {
            player.setMuted(newMuted)
            resolve(nil)
        }
    }

    @objc
    func setAudioTrack(
        _ instanceId: String!,
        trackId: String,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.setAudioTrack(trackId)
            resolve(nil)
        }
    }

    @objc
    func setVideoTrack(
        _ instanceId: String!,
        trackId: String,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.setVideoTrack(trackId)
            resolve(nil)
        }
    }

    @objc
    func setVideoRendition(
        _ instanceId: String!,
        renditionId: String,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.setVideoRendition(renditionId)
            resolve(nil)
        }
    }

    @objc
    func setTextTrack(
        _ instanceId: String!,
        trackId: String?,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.setTextTrack(trackId)
            resolve(nil)
        }
    }

    @objc
    func enableAdaptiveVideo(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.enableAdaptiveVideo()
            resolve(nil)
        }
    }

    @objc
    func supportsPictureInPicture(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            resolve(player.supportsPictureInPicture())
        }
    }

    @objc
    func isInPictureInPictureMode(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            resolve(player.isInPictureInPictureMode())
        }
    }

    @objc
    func enterPictureInPictureMode(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.enterPictureInPictureMode()
            resolve(nil)
        }
    }

    @objc
    func exitPictureInPictureMode(
        _ instanceId: String!,
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        guard let player = Repository.shared.players.get(key: instanceId) else {
            Rejecter(reject).reject(PrestoPlayError(.fatal, .playerNotFound))
            return
        }
        DispatchQueue.main.async {
            player.exitPictureInPictureMode()
            resolve(nil)
        }
    }
}

@available(tvOS 14.0, *)
extension PlayerModule {
    public override func invalidate() {
        let players = Repository.shared.players.removeAll()
        for player in players {
            player.destroy()
        }
    }
}
