//
//  StoryBlock.swift
//  react-native-firework-sdk
//
//  Created by linjie jiang on 2022/8/11.
//

// swiftlint:disable file_length

import FireworkVideo
import UIKit

@objc
public enum StoryBlockSourceType: Int {
    case discover, channel, playlist, dynamicContent, hashtagPlaylist, sku, singleContent
}

public protocol StoryBlockViewDelegate: AnyObject {
    func storyBlockDidLoadFeed(_ view: StoryBlock)
    func storyBlock(_ view: StoryBlock, didFailToLoadFeed error: StoryBlockError)
}

// swiftlint:disable type_body_length
@objc
public class StoryBlock: UIView, StoryBlockViewControllerDelegate, PictureInPictureControllerDelegate {
    @objc public var sourceType: StoryBlockSourceType = .discover
    @objc public var channel: String = ""
    @objc public var playlist: String = ""
    @objc public var dynamicContentParameters: NSDictionary = NSDictionary()
    @objc public var hashtagFilterExpression: String = ""
    @objc public var productIds: NSArray = NSArray()
    @objc public var contentId: String = ""
    @objc public var adConfiguration: AdConfiguration?
    @objc public var storyBlockConfiguration: StoryBlockConfiguration? {
        didSet {
            guard let storyBlockVC = storyBlockVC else {
                return
            }

            if storyBlockConfiguration?.videoPlayerLogoConfiguration != oldValue?.videoPlayerLogoConfiguration {
                storyBlockVC.isPictureInPictureEnabled = false
                storyBlockVC.detachFromParent()
                self.storyBlockVC = nil
                DispatchQueue.main.async {
                    self.embed()
                }
            } else {
                updateStoryBlockVCConfiguration()

                if let enableSystemPiP = storyBlockConfiguration?.enableSystemPictureInPicture {
                    var pipConfig = storyBlockVC.pictureInPictureConfiguration
                    pipConfig.isSystemPictureInPictureEnabled = enableSystemPiP
                    storyBlockVC.pictureInPictureConfiguration = pipConfig
                }
            }
        }
    }

    public weak var delegate: StoryBlockViewDelegate?

    @objc var onStoryBlockLoadFinished: RCTBubblingEventBlock?
    @objc var onStoryBlockEmpty: RCTBubblingEventBlock?

    @objc var onStoryBlockDidStartPictureInPicture: RCTBubblingEventBlock?

    @objc var onStoryBlockDidStopPictureInPicture: RCTBubblingEventBlock?

    @objc var onStoryBlockGetFeedId: RCTBubblingEventBlock?

    private var storyBlockVC: StoryBlockViewController?

    private var source: StoryBlockContentSource {
        switch sourceType {
        case .discover:
            return .discover
        case .channel:
            return .channel(channelID: channel)
        case .playlist:
            return .channelPlaylist(channelID: channel, playlistID: playlist)
        case .dynamicContent:
            let parameters = dynamicContentParameters as? [String: [String]]
            return .dynamicContent(channelID: channel, parameters: parameters ?? [:])
        case .hashtagPlaylist:
            return .hashtagPlaylist(channelID: channel, filterExpression: hashtagFilterExpression)
        case .sku:
            return .skuPlaylist(channelID: channel, productIDs: (productIds as? [String]) ?? [])
        case .singleContent:
            return .singleContent(contentID: contentId)
        }
    }
    private var isInPip: Bool = false

    public override init(frame: CGRect) {
        super.init(frame: frame)
        self.clipsToBounds = true
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.clipsToBounds = true
    }

    public override func layoutSubviews() {
        super.layoutSubviews()
        embed()
    }

    private func embed() {
        guard let parentVC = fws_parentViewController else {
            return
        }
        guard self.storyBlockVC == nil else {
            return
        }

        var resultStoryBlockVC: StoryBlockViewController?
        if let fireworkVideoAdConfiguration = RCTConvert.getFireworkVideoAdConfiguration(adConfiguration) {
            resultStoryBlockVC = StoryBlockViewController(source: source, adConfiguration: fireworkVideoAdConfiguration)
        } else {
            resultStoryBlockVC = StoryBlockViewController(source: source)
        }

        guard let storyBlockVC = resultStoryBlockVC else {
            return
        }

        onStoryBlockGetFeedId?(["feedId": storyBlockVC.feedID])

        self.storyBlockVC = storyBlockVC
        updateStoryBlockVCConfiguration()

        storyBlockVC.isPictureInPictureEnabled = storyBlockConfiguration?.enablePictureInPicture ?? false

        if let enableSystemPiP = storyBlockConfiguration?.enableSystemPictureInPicture {
            var pipConfig = storyBlockVC.pictureInPictureConfiguration
            pipConfig.isSystemPictureInPictureEnabled = enableSystemPiP
            storyBlockVC.pictureInPictureConfiguration = pipConfig
        }

        storyBlockVC.pictureInPictureDelegate = self
        storyBlockVC.delegate = self

        parentVC.attachChild(storyBlockVC, to: self)

        // Replay an enter that arrived before the VC existed. A left is not
        // replayed: a fresh VC starts idle.
        if hasViewportEnterIntent {
            storyBlockVC.onViewportEntered()
        }
    }

    public override func removeFromSuperview() {
        super.removeFromSuperview()

        guard let storyBlockVC = self.storyBlockVC else {
            return
        }
        if !isInPip {
            storyBlockVC.isPictureInPictureEnabled = false
        }
        storyBlockVC.detachFromParent()
        self.storyBlockVC = nil
    }

    @objc
    public func play() {
        guard let storyBlockVC = self.storyBlockVC else {
            return
        }
        storyBlockVC.play()
    }

    @objc
    public func pause() {
        guard let storyBlockVC = self.storyBlockVC else {
            return
        }
        storyBlockVC.pause()
    }

    // MARK: - Viewport intent
    //
    // Latest explicit enter/left intent from RN. Kept because an enter can
    // arrive before the player VC exists (RN commands can beat the layout pass
    // that embeds) — it is replayed once the VC is attached, which also covers
    // the VC being re-created on configuration changes.
    private var hasViewportEnterIntent = false

    @objc
    public func onViewportEntered() {
        hasViewportEnterIntent = true
        storyBlockVC?.onViewportEntered()
    }

    @objc
    public func onViewportLeft() {
        hasViewportEnterIntent = false
        storyBlockVC?.onViewportLeft()
    }

    @objc
    public func openFullscreen() {
        guard let storyBlockVC = self.storyBlockVC else {
            return
        }
        storyBlockVC.enterFullScreen()
    }

    public func storyBlockDidLoadFeed(_ viewController: StoryBlockViewController) {
        guard let delegate = self.delegate else {
            return
        }

        delegate.storyBlockDidLoadFeed(self)
    }

    public func storyBlock(
        _ viewController: StoryBlockViewController, didFailToLoadFeed error: StoryBlockError
    ) {
        guard let delegate = self.delegate else {
            return
        }

        delegate.storyBlock(self, didFailToLoadFeed: error)
    }

    // swiftlint:disable:next cyclomatic_complexity function_body_length
    private func updateStoryBlockVCConfiguration() {
        guard let storyBlockVC = storyBlockVC else {
            return
        }

        var resultConfig = FireworkVideo.StoryBlockConfiguration()
        resultConfig.viewport.useSafeAreaViewport = false
        // The first display mute state comes from the SDK init options, not from
        // the story block config, so apply it before the early return below;
        // otherwise it is skipped whenever JS provides no storyBlockConfiguration.
        applyLaunchBehaviorFirstDisplay(to: &resultConfig)
        guard let config = storyBlockConfiguration else {
            storyBlockVC.viewConfiguration = resultConfig
            return
        }

        if let playerStyle = config.playerStyle {
            switch playerStyle {
            case .full:
                resultConfig.playerStyle = .fullBleed
                resultConfig.fullScreenPlayerView.playerStyle = .fullBleed
            case .fit:
                resultConfig.playerStyle = .fit
                resultConfig.fullScreenPlayerView.playerStyle = .fit
            }
        }

        if let videoCompleteAction = config.videoCompleteAction {
            switch videoCompleteAction {
            case .loop:
                resultConfig.fullScreenPlayerView.videoCompleteAction = .loop
            default:
                resultConfig.fullScreenPlayerView.videoCompleteAction = .advanceToNext
            }
        }

        if let showShareButton = config.showShareButton {
            resultConfig.shareButton.isHidden = !showShareButton
            resultConfig.fullScreenPlayerView.shareButton.isHidden = !showShareButton
        }
        if let shareButtonPosition = config.shareButtonPosition {
            switch shareButtonPosition {
            case .videoDetailPage:
                resultConfig.shareButton.position = .videoDetailPage
                resultConfig.fullScreenPlayerView.shareButton.position = .videoDetailPage
            case .playerTopArea:
                resultConfig.shareButton.position = .playerTopArea
                resultConfig.fullScreenPlayerView.shareButton.position = .playerTopArea
            }
        }

        if let ctaButtonStyle = config.ctaButtonStyle {
            if let backgroundColor = ctaButtonStyle.backgroundColor, let color = backgroundColor.uicolor() {
                resultConfig.ctaButton.contentConfiguration.backgroundColor = color
                resultConfig.fullScreenPlayerView.ctaButton.contentConfiguration.backgroundColor = color
            }
            if let textcolor = ctaButtonStyle.textColor, let color = textcolor.uicolor() {
                resultConfig.ctaButton.contentConfiguration.textColor = color
                resultConfig.fullScreenPlayerView.ctaButton.contentConfiguration.textColor = color
            }
            if let fontSize = ctaButtonStyle.fontSize {
                let iOSFontInfo = ctaButtonStyle.iOSFontInfo ?? FontInfo()
                resultConfig.ctaButton.contentConfiguration.font = iOSFontInfo.getFont(fontSize)
                resultConfig.fullScreenPlayerView.ctaButton.contentConfiguration.font = iOSFontInfo.getFont(fontSize)
            }
            if let shape = ctaButtonStyle.shape {
                resultConfig.ctaButton.contentConfiguration.shape = shape.shape()
                resultConfig.fullScreenPlayerView.ctaButton.contentConfiguration.shape = shape.shape()
            }
        }

        if let showPlaybackButton = config.showPlaybackButton {
            resultConfig.playbackButton.isHidden = !showPlaybackButton
            resultConfig.fullScreenPlayerView.playbackButton.isHidden = !showPlaybackButton
        } else {
            resultConfig.playbackButton.isHidden = false
            resultConfig.fullScreenPlayerView.playbackButton.isHidden = false
        }

        if let showMuteButton = config.showMuteButton {
            resultConfig.muteButton.isHidden = !showMuteButton
            resultConfig.fullScreenPlayerView.muteButton.isHidden = !showMuteButton
        }

        if let showBranding = config.showBranding {
            resultConfig.videoDetail.fireworkAttribution.isHidden = !showBranding
            resultConfig.fullScreenPlayerView.videoDetail.fireworkAttribution.isHidden = !showBranding
        }

        if let showVideoDetailTitle = config.showVideoDetailTitle {
            resultConfig.videoDetail.titleConfiguration.isHidden = !showVideoDetailTitle
            resultConfig.fullScreenPlayerView.videoDetail.titleConfiguration.isHidden = !showVideoDetailTitle
        }

        if let shareBaseURL = config.shareBaseURL {
            resultConfig.shareButton.behavior.baseURL = URL(string: shareBaseURL)
            resultConfig.fullScreenPlayerView.shareButton.behavior.baseURL = URL(string: shareBaseURL)
        } else if let shareBaseUrl = gShareBaseURL {
            resultConfig.shareButton.behavior.baseURL = URL(string: shareBaseUrl)
            resultConfig.fullScreenPlayerView.shareButton.behavior.baseURL = URL(string: shareBaseUrl)
        }

        if let ctaWidth = config.ctaWidth {
            resultConfig.ctaButton.width = ctaWidth.width()
            resultConfig.fullScreenPlayerView.ctaButton.width = ctaWidth.width()
        }

        if let ctaDelay = config.ctaDelay,
           let type = ctaDelay.type,
           let value = ctaDelay.value {
            switch type {
            case .constant:
                resultConfig.ctaButton.behavior.delay = .constant(value)
                resultConfig.fullScreenPlayerView.ctaButton.behavior.delay = .constant(value)
            case .percentage:
                resultConfig.ctaButton.behavior.delay = .percentage(Float(value))
                resultConfig.fullScreenPlayerView.ctaButton.behavior.delay = .percentage(Float(value))
            }
        }

        if let ctaHighlightDelay = config.ctaHighlightDelay,
           let type = ctaHighlightDelay.type,
           let value = ctaHighlightDelay.value {
            switch type {
            case .constant:
                resultConfig.ctaButton.behavior.highlightDelay = .constant(value)
                resultConfig.fullScreenPlayerView.ctaButton.behavior.highlightDelay = .constant(value)
            case .percentage:
                resultConfig.ctaButton.behavior.highlightDelay = .percentage(Float(value))
                resultConfig.fullScreenPlayerView.ctaButton.behavior.highlightDelay = .percentage(Float(value))
            }
        }

        if let buttonConfiguration = config.buttonConfiguration {
            if let videoDetailButtonDisplay = buttonConfiguration.videoDetailButton?.getButtonDisplayConfiguration() {
                resultConfig.videoDetailButtonDisplayConfiguration = videoDetailButtonDisplay
                resultConfig.fullScreenPlayerView.videoDetailButtonDisplayConfiguration = videoDetailButtonDisplay
            }

            if let closeButtonDisplay = buttonConfiguration.closeButton?.getButtonDisplayConfiguration() {
                resultConfig.fullScreenPlayerView.closeButtonConfiguration.closeDisplay = closeButtonDisplay
            }

            if let muteButtonDisplay = buttonConfiguration.muteButton?.getButtonDisplayConfiguration() {
                resultConfig.muteButton.muteDisplay = muteButtonDisplay
                resultConfig.fullScreenPlayerView.muteButton.muteDisplay = muteButtonDisplay
            }

            if let unmuteButtonDisplay = buttonConfiguration.unmuteButton?.getButtonDisplayConfiguration() {
                resultConfig.muteButton.unmuteDisplay = unmuteButtonDisplay
                resultConfig.fullScreenPlayerView.muteButton.unmuteDisplay = unmuteButtonDisplay
            }

            if let playButtonDisplay = buttonConfiguration.playButton?.getButtonDisplayConfiguration() {
                resultConfig.playbackButton.playDisplay = playButtonDisplay
                resultConfig.fullScreenPlayerView.playbackButton.playDisplay = playButtonDisplay
            }

            if let pauseButtonDisplay = buttonConfiguration.pauseButton?.getButtonDisplayConfiguration() {
                resultConfig.playbackButton.pauseDisplay = pauseButtonDisplay
                resultConfig.fullScreenPlayerView.playbackButton.pauseDisplay = pauseButtonDisplay
            }
        }

        if let showMoreButton = config.showMoreButton {
            resultConfig.videoDetailButton.isHidden = !showMoreButton
            resultConfig.fullScreenPlayerView.videoDetailButton.isHidden = !showMoreButton
        }

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

        if let videoPlayerLogoConfiguration = config.videoPlayerLogoConfiguration {
            switch videoPlayerLogoConfiguration.option {
            case .disabled:
                resultConfig.logoConfiguration.option = .disabled
                resultConfig.fullScreenPlayerView.logoConfiguration.option = .disabled
            case .creator:
                if let encodedId = videoPlayerLogoConfiguration.encodedId {
                    resultConfig.logoConfiguration.option = .creator(encodedId)
                    resultConfig.fullScreenPlayerView.logoConfiguration.option = .creator(encodedId)
                }
            case .channelAggregator:
                if let encodedId = videoPlayerLogoConfiguration.encodedId {
                    resultConfig.logoConfiguration.option = .channelAggregator(encodedId)
                    resultConfig.fullScreenPlayerView.logoConfiguration.option = .channelAggregator(encodedId)
                }
            }

            if let isClickable = videoPlayerLogoConfiguration.isClickable {
                resultConfig.logoConfiguration.isEnabled = isClickable
                resultConfig.fullScreenPlayerView.logoConfiguration.isEnabled = isClickable
            }
        }

        if let replayBadgeConfiguration = config.replayBadgeConfiguration {
            resultConfig.replayBadge.isHidden = replayBadgeConfiguration.isHidden
            resultConfig.fullScreenPlayerView.replayBadge.isHidden = replayBadgeConfiguration.isHidden
        }

        if let countdownTimerConfiguration = config.countdownTimerConfiguration {
            resultConfig.countdownTimerConfiguration = countdownTimerConfiguration.fwCountdownTimerConfiguration()
            // swiftlint:disable:next line_length
            resultConfig.fullScreenPlayerView.countdownTimerConfiguration = countdownTimerConfiguration.fwCountdownTimerConfiguration()
        }

        if let additionalControlsInset = config.additionalControlsInset {
            resultConfig.additionalControlsInset = .init(
                top: additionalControlsInset.top ?? 0,
                left: 0,
                bottom: additionalControlsInset.bottom ?? 0,
                right: 0)
        }

        if let shouldExtendMediaOutsideSafeArea = config.shouldExtendMediaOutsideSafeArea {
            resultConfig.fullScreenPlayerView.shouldExtendMediaOutsideSafeArea = shouldExtendMediaOutsideSafeArea
        }

        if let statusBarHidden = config.statusBarHidden {
            resultConfig.fullScreenPlayerView.statusBarHidden = statusBarHidden
        }

        if let statusBarStyle = config.statusBarStyle {
            resultConfig.fullScreenPlayerView.statusBarStyle = statusBarStyle.toUIStatusBarStyle()
        }

        if let pipPlacement = config.pipPlacement {
            resultConfig.fullScreenPlayerView.pipPlacement = pipPlacement.convertToFWPipPlacement()
        }

        if let scrollDirection = config.scrollDirection {
            switch scrollDirection {
            case .horizontal:
                resultConfig.scrollDirection = .horizontal
                resultConfig.fullScreenPlayerView.scrollDirection = .horizontal
            case .vertical:
                resultConfig.scrollDirection = .vertical
                resultConfig.fullScreenPlayerView.scrollDirection = .vertical
            }
        }

        if let enableScrollForVertical = config.enableScrollForVertical {
            resultConfig.enableScrollForVertical = enableScrollForVertical
        }

        if let isArrowButtonVisible = config.isArrowButtonVisible {
            resultConfig.scrollNavigation.isArrowButtonVisible = isArrowButtonVisible
        }

        if let isFullscreenArrowButtonVisible = config.isFullscreenArrowButtonVisible {
            resultConfig.fullScreenPlayerView.scrollNavigation.isArrowButtonVisible = isFullscreenArrowButtonVisible
        }

        if let feedCompleteAction = config.feedCompleteAction {
            switch feedCompleteAction {
            case .loop:
                resultConfig.fullScreenPlayerView.feedCompleteAction = .loop
            case .dismiss:
                resultConfig.fullScreenPlayerView.feedCompleteAction = .dismiss
            }
        }

        if let handleAppearanceManually = config.handleAppearanceManually {
            resultConfig.managesAppearanceManually = handleAppearanceManually
        }

        storyBlockVC.viewConfiguration = resultConfig
    }

    private func applyLaunchBehaviorFirstDisplay(
        to config: inout FireworkVideo.StoryBlockConfiguration
    ) {
        if let storyBlockBehavior = gVideoLaunchBehavior?.storyBlockBehavior() {
            config.onFirstDisplay = storyBlockBehavior
        } else {
            config.onFirstDisplay = .unmuted
        }
    }

    // MARK: PictureInPictureDelegate

    public func pictureInPictureControllerWillStartPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {
    }

    public func pictureInPictureControllerDidStartPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {
        self.onStoryBlockDidStartPictureInPicture?(nil)
        isInPip = true
    }

    public func pictureInPictureController(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController,
        failedToStartPictureInPictureWithError error: Error?
    ) {
    }

    public func pictureInPictureControllerWillStopPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {
    }

    public func pictureInPictureControllerDidStopPictureInPicture(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController
    ) {
        self.onStoryBlockDidStopPictureInPicture?(nil)
        isInPip = false
    }

    public func pictureInPictureController(
        _ pictureInPictureController: FireworkVideo.PictureInPictureController,
        restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
    ) {
        completionHandler(true)
    }
}
// swiftlint:enable type_body_length
