//
//  VideoFeedManager.m
//  react-native-firework-sdk
//
//  Created by Jeff Zheng on 2021/12/22.
//
//  The FireworkSDKModule Wrap native FireworkVideoSDK class,then expose to JavaScript in Objective-C

import FireworkVideo
import Network

// swiftlint:disable file_length

var gShareBaseURL: String?
var gAdBadgeConfiguration: AdBadgeConfiguration?
var gVideoLaunchBehavior: FWRVideoLaunchBehavior?

// swiftlint:disable type_body_length
@objc(FireworkSDKModule)
class FireworkSDKModule: RCTEventEmitter, FireworkVideoSDKDelegate {
    static var shared: FireworkSDKModule?
    static var cachedSDKInitOptions: SDKInitOptions?
    static var hasCachedSDKInitOptions: Bool = false

    //    var shareBaseURL: String?
    var customCTAClickEnabled: Bool = false

    /// Decide whether to send video playback event to RN.
    var enableVideoPlayBackEvent: Bool = false

    // player view controller
    var playerMap: NSMapTable<NSString, UIViewController> = NSMapTable.strongToWeakObjects()

    private var hasCalledSDKInit: Bool = false
    private var argumentsForInitEvent: [String: Any]?
    private var hasListeners = false
    private let monitor = NWPathMonitor()
    private var scheduledInitializingSDKClosure: (() -> Void)?

    override init() {
        super.init()
        Self.shared = self
        if Self.hasCachedSDKInitOptions {
            self.initializeSDK(Self.cachedSDKInitOptions, true)
        }

        UIWindowScene.swizzleMethods()

        _ = FWSAppLanguageManager.shared
        NotificationCenter.default.addObserver(
            self, selector: #selector(Self.onAppLanguageChanged),
            name: FWSAppLanguageManager.NotificationName.AppLanguageChanged, object: nil)
        startMonitoringNetworkStatus()
    }

    override func supportedEvents() -> [String]! {
        FWEventName.allCases.map { $0.rawValue }
    }

    override class func requiresMainQueueSetup() -> Bool {
        return true
    }

    override var methodQueue: DispatchQueue {
        return DispatchQueue.main
    }

    static func initializeSDK(_ options: SDKInitOptions?) {
        if let shared = shared {
            shared.initializeSDK(options, true)
        }
        hasCachedSDKInitOptions = true
        cachedSDKInitOptions = options
    }

    @objc(markInitCalled:rejecter:)
    func markInitCalled(
        _ resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock
    ) {
        var initSucceeded = false
        if let argumentsForInitEvent = argumentsForInitEvent {
            // The sdk init event was received before.
            sendEvent(withName: FWEventName.sdkInit.rawValue, body: argumentsForInitEvent)
            initSucceeded = argumentsForInitEvent["error"] == nil
        }

        resolver(initSucceeded)
    }

    func initializeSDK(_ options: SDKInitOptions?, _ fromNative: Bool) {
        if hasCalledSDKInit {
            if hasListeners, let argumentsForInitEvent = argumentsForInitEvent {
                sendEvent(withName: FWEventName.sdkInit.rawValue, body: argumentsForInitEvent)
            }
        } else if FireworkVideoSDK.isInitialized {
            argumentsForInitEvent = [:]
            gVideoLaunchBehavior = options?.videoLaunchBehavior
            FireworkVideoSDK.ctaDelegate = self
            FireworkVideoSDK.eventTracking.videoPlaybackDelegate = self
            FireworkVideoSDK.eventTracking.feedDelegate = self
            if hasListeners {
                sendEvent(withName: FWEventName.sdkInit.rawValue, body: argumentsForInitEvent)
            }
        } else {
            let initializingSDKClosure = {
                gVideoLaunchBehavior = options?.videoLaunchBehavior

                FireworkVideoSDK.initializeSDK(delegate: self)
                FireworkVideoSDK.ctaDelegate = self
                FireworkVideoSDK.eventTracking.videoPlaybackDelegate = self
                FireworkVideoSDK.eventTracking.feedDelegate = self
            }

            if monitor.currentPath.status == .satisfied {
                #if DEBUG
                print("call initializingSDKClosure directly")
                #endif
                initializingSDKClosure()
            } else {
                #if DEBUG
                print("schedule initializingSDKClosure")
                #endif
                self.scheduledInitializingSDKClosure = initializingSDKClosure
            }
        }

        hasCalledSDKInit = true
    }

    @objc(openVideoPlayer:config:)
    func openVideoPlayer(_ url: String, config: NSDictionary?) {
        guard let urlString = URL.init(string: url) else {
            return
        }

        var enablePictureInPicture: Bool?
        var enableSystemPictureInPicture: Bool?
        var feedContentConfig = VideoFeedContentConfiguration()
        if let config = config as? [String: Any] {
            let videoPlayerConfig = RCTConvert.videoPlayerConfiguration(config)
            enablePictureInPicture = videoPlayerConfig?.enablePictureInPicture
            enableSystemPictureInPicture = videoPlayerConfig?.enableSystemPictureInPicture
            let vpcConfig = VideoFeed.convertToVideoPlayerContentConfiguration(videoPlayerConfig)

            if let vpcConfig = vpcConfig {
                feedContentConfig.playerView = vpcConfig
            }

            if let adBadge = FireworkSDKModule.convertToFWAdBadgeConfiguration(gAdBadgeConfiguration) {
                feedContentConfig.adBadge = adBadge
            }
        }

        guard let presentedVC = RCTPresentedViewController() else {
            return
        }

        VideoFeedViewController.openVideoPlayer(
            with: urlString,
            feedContentConfig,
            presentedVC,
            enablePictureInPicture ?? false) { result in
                if let enableSystemPiP = enableSystemPictureInPicture,
                   case let .success(feedVC) = result {
                    var pipConfig = feedVC.pictureInPictureConfiguration
                    pipConfig.isSystemPictureInPictureEnabled = enableSystemPiP
                    feedVC.pictureInPictureConfiguration = pipConfig
                }
            }
    }

    @objc(openVideoPlayerWithSource:)
    func openVideoPlayerWithSource(_ params: NSDictionary?) {
        guard let params = params as? [String: Any] else {
            return
        }

        guard let contentSource = Self.buildVideoFeedContentSource(from: params) else {
            return
        }

        var enablePictureInPicture: Bool?
        var enableSystemPictureInPicture: Bool?
        var feedContentConfig = VideoFeedContentConfiguration()
        if let config = params["config"] as? [String: Any] {
            let videoPlayerConfig = RCTConvert.videoPlayerConfiguration(config)
            enablePictureInPicture = videoPlayerConfig?.enablePictureInPicture
            enableSystemPictureInPicture = videoPlayerConfig?.enableSystemPictureInPicture
            let vpcConfig = VideoFeed.convertToVideoPlayerContentConfiguration(videoPlayerConfig)

            if let vpcConfig = vpcConfig {
                feedContentConfig.playerView = vpcConfig
            }

            if let adBadge = FireworkSDKModule.convertToFWAdBadgeConfiguration(gAdBadgeConfiguration) {
                feedContentConfig.adBadge = adBadge
            }
        }

        VideoFeedViewController.openVideoPlayer(
            source: contentSource,
            feedConfiguration: feedContentConfig,
            isPictureInPictureEnabled: enablePictureInPicture ?? false
        ) { result in
            if let enableSystemPiP = enableSystemPictureInPicture,
               case let .success(feedVC) = result {
                var pipConfig = feedVC.pictureInPictureConfiguration
                pipConfig.isSystemPictureInPictureEnabled = enableSystemPiP
                feedVC.pictureInPictureConfiguration = pipConfig
            }
        }
    }

    // swiftlint:disable cyclomatic_complexity
    private static func buildVideoFeedContentSource(from params: [String: Any]) -> VideoFeedContentSource? {
        guard let sourceType = params["source"] as? String else {
            return nil
        }

        switch sourceType {
        case "discover":
            return .discover
        case "channel":
            guard let channelId = params["channel"] as? String else { return nil }
            return .channel(channelID: channelId)
        case "playlist":
            guard let channelId = params["channel"] as? String,
                  let playlistId = params["playlist"] as? String else { return nil }
            return .channelPlaylist(channelID: channelId, playlistID: playlistId)
        case "playlistGroup":
            guard let groupId = params["playlistGroup"] as? String else { return nil }
            return .playlistGroup(groupID: groupId)
        case "dynamicContent":
            guard let channelId = params["channel"] as? String,
                  let parameters = params["dynamicContentParameters"] as? [String: [String]] else { return nil }
            return .dynamicContent(channelID: channelId, parameters: parameters)
        case "hashtagPlaylist":
            guard let channelId = params["channel"] as? String,
                  let filterExpression = params["hashtagFilterExpression"] as? String else { return nil }
            return .hashtagPlaylist(channelID: channelId, filterExpression: filterExpression)
        case "sku":
            guard let channelId = params["channel"] as? String,
                  let productIds = params["productIds"] as? [String] else { return nil }
            return .skuPlaylist(channelID: channelId, productIDs: productIds)
        case "singleContent":
            guard let contentId = params["contentId"] as? String else { return nil }
            return .singleContent(contentID: contentId)
        case "videoAds":
            guard let adChannelId = params["adChannelId"] as? String,
                  let vastXml = params["vastXml"] as? String else { return nil }
            return .videoAds(adChannelId: adChannelId, vastXml: vastXml)
        case "channelVideos":
            guard let channelId = params["channel"] as? String,
                  let videoIds = params["videoIds"] as? [String] else { return nil }
            return .channelVideos(channelID: channelId, videoIDs: videoIds)
        default:
            return nil
        }
    }

    @objc(setVideoFeedClickCallbackEnabled:)
    func setVideoFeedClickCallbackEnabled(_ enabled: Bool) {
        if enabled {
            FireworkVideoSDK.eventTracking.feedDelegate = self
        }
    }

    @objc(setCustomCTAClickEnabled:)
    func setCustomCTAClickEnabled(_ enabled: Bool) {
        customCTAClickEnabled = enabled
    }

    @objc(setShareBaseURL:resolver:rejecter:)
    func setShareBaseURL(
        _ url: String, resolver: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock
    ) {
        gShareBaseURL = url
        resolver([:])
    }

    @objc(setVideoPlaybackEventEnabled:)
    func setVideoPlaybackEventEnabled(_ enable: Bool) {
        enableVideoPlayBackEvent = enable
    }

    @objc(setAdBadgeConfiguration:resolver:rejecter:)
    func setAdBadgeConfiguration(
        _ config: NSDictionary? = nil, resolver: RCTPromiseResolveBlock,
        rejecter: RCTPromiseRejectBlock
    ) {
        guard let config = config as? [String: Any] else {
            resolver([:])
            return
        }
        gAdBadgeConfiguration = RCTConvert.adBadgeConfiguration(config)
        resolver([:])
    }

    @objc(trackPurchase:)
    func trackPurchase(_ parameters: NSDictionary?) {
        guard let parameters = parameters as? [String: Any] else {
            return
        }

        guard let trackPurchaseParameters = RCTConvert.trackPurchaseParameters(parameters) else {
            return
        }
        FireworkVideoSDK.trackPurchase(
            orderID: trackPurchaseParameters.orderId,
            orderValue: trackPurchaseParameters.value?.fwDecimalValue ?? 0,
            currencyCode: trackPurchaseParameters.currencyCode ?? "",
            countryCode: trackPurchaseParameters.countryCode,
            shippingPrice: trackPurchaseParameters.shippingPrice?.fwDecimalValue,
            subtotal: trackPurchaseParameters.subtotal?.fwDecimalValue ?? 0,
            totalDiscounts: trackPurchaseParameters.totalDiscounts?.fwDecimalValue,
            lineItems: trackPurchaseParameters.products?.map { $0.generateFWLineItem() } ?? [],
            trackPurchaseParameters.additionalInfo
        )
    }

    @objc(changeAppLanguage:resolver:rejecter:)
    func changeAppLanguage(
        _ language: String?,
        resolver: @escaping RCTPromiseResolveBlock,
        rejecter: @escaping RCTPromiseRejectBlock
    ) {
        let result = FWSAppLanguageManager.shared.changeAppLanguage(language)
        resolver(result)
    }

    @objc(setLivestreamPlayerDesignVersion:resolver:rejecter:)
    func setLivestreamPlayerDesignVersion(
        _ version: String,
        resolver: @escaping RCTPromiseResolveBlock,
        rejecter: @escaping RCTPromiseRejectBlock
    ) {
        if let livestreamPlayerDesignVersion = RCTConvert.livestreamPlayerDesignVersion(version) {
            FireworkVideoSDK.livestreamPlayerDesignVersion = livestreamPlayerDesignVersion
            resolver(true)
        } else {
            resolver(false)
        }
    }

    @objc(setShortVideoPlayerDesignVersion:resolver:rejecter:)
    func setShortVideoPlayerDesignVersion(
        _ version: String,
        resolver: @escaping RCTPromiseResolveBlock,
        rejecter: @escaping RCTPromiseRejectBlock
    ) {
        if let shortVideoPlayerDesignVersion = RCTConvert.shortVideoPlayerDesignVersion(version) {
            FireworkVideoSDK.shortVideoPlayerDesignVersion = shortVideoPlayerDesignVersion
            resolver(true)
        } else {
            resolver(false)
        }
    }

    override func startObserving() {
        super.startObserving()
        hasListeners = true
    }

    override func stopObserving() {
        super.stopObserving()
        hasListeners = false
    }

    // MARK: - FireworkVideoSDKDelegate
    func fireworkVideoDidLoadSuccessfully() {
        DispatchQueue.main.async {
            self.argumentsForInitEvent = [:]

            if self.hasListeners {
                self.sendEvent(
                    withName: FWEventName.sdkInit.rawValue,
                    body: self.argumentsForInitEvent
                )
            }
        }
    }

    func fireworkVideoDidLoadWith(error: FireworkVideoSDKError) {
        DispatchQueue.main.async {
            self.argumentsForInitEvent = [
                "error": [
                    "name": error.jsErrorName, "reason": error.recoverySuggestion ?? "default"
                ]
            ]

            if self.hasListeners {
                self.sendEvent(
                    withName: FWEventName.sdkInit.rawValue,
                    body: self.argumentsForInitEvent
                )
            }
        }
    }

    @objc func onAppLanguageChanged() {
        if self.hasListeners {
            sendEvent(
                withName: FWEventName.nativeAppLanguageUpdated.rawValue,
                body: [
                    "appLanguage": FWSAppLanguageManager.shared.appLanguage ?? ""
                ])
        }
    }

    func startMonitoringNetworkStatus() {
        monitor.pathUpdateHandler = { path in
            if path.status == .satisfied {
                #if DEBUG
                print("Network access is allowed.")
                #endif
                DispatchQueue.main.async {
                    if let initializingSDKClosure = self.scheduledInitializingSDKClosure {
                        #if DEBUG
                        print("call scheduledInitializingSDKClosure")
                        #endif
                        initializingSDKClosure()
                        self.scheduledInitializingSDKClosure = nil
                    }
                }
            } else {
                #if DEBUG
                print("Network access is not allowed.")
                #endif
            }
        }
        let queue = DispatchQueue(label: "FWNetworkMonitor")
        monitor.start(queue: queue)
    }

    func cancelMonitoringNetworkStatus() {
        monitor.pathUpdateHandler = nil
        monitor.cancel()
    }
}

extension FireworkSDKModule {
    static func convertToFWAdBadgeConfiguration(_ adBadgeConfiguration: AdBadgeConfiguration?)
        -> FireworkVideo.AdBadgeConfiguration? {
        guard let adBadgeConfiguration = adBadgeConfiguration else {
            return nil
        }

        var fwAdBadgeConfiguration = FireworkVideo.AdBadgeConfiguration()
        if let textColor = adBadgeConfiguration.textColor, let color = textColor.uicolor() {
            fwAdBadgeConfiguration.textColor = color
        }
        if let backgroundColor = adBadgeConfiguration.backgroundColor, let color = backgroundColor.uicolor() {
            fwAdBadgeConfiguration.backgroundColor = color
        }
        switch adBadgeConfiguration.badgeTextType {
        case .sponsored:
            fwAdBadgeConfiguration.badgeText =
                FireworkVideo.AdBadgeConfiguration.BadgeText.sponsored
        default:
            fwAdBadgeConfiguration.badgeText = FireworkVideo.AdBadgeConfiguration.BadgeText.ad
        }

        return fwAdBadgeConfiguration
    }
}
// swiftlint:enable type_body_length
