//
//  NamiCampaignManagerBridge.swift
//  RNNami
//
//  Copyright © 2020-2025 Nami ML Inc. All rights reserved.
//

import Foundation
import NamiApple
import os
import React

@objc(RNNamiCampaignManager)
class RNNamiCampaignManager: RCTEventEmitter {
    public static var shared: RNNamiCampaignManager?

    override init() {
        super.init()
    }

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

    private var hasListeners = false
    override func startObserving() { hasListeners = true }
    override func stopObserving() { hasListeners = false }

    private func safeSend(withName name: String, body: Any?) {
        guard hasListeners else {
            print("[RNNamiCampaignManager] Warning: no listeners, so event not being sent to JS.")
            return
        } // optional but avoids warnings
        sendEvent(withName: name, body: body)
    }

    override func supportedEvents() -> [String]! {
        return ["AvailableCampaignsChanged", "NamiPaywallEvent"]
    }

    private func campaignInToDictionary(_ campaign: NamiCampaign) -> NSDictionary {
        let dictionary: [String: Any?] = [
            "id": campaign.id,
            "rule": campaign.rule,
            "segment": campaign.segment,
            "paywall": campaign.paywall,
            "type": campaign.type.rawValue,
            "value": campaign.value,
        ]
        return NSDictionary(dictionary: dictionary.compactMapValues { $0 })
    }

    func isURL(string: String) -> Bool {
        if let url = URL(string: string), url.scheme != nil, url.host != nil {
            return true
        }
        return false
    }

    private func mapPaywallActionToJS(_ action: NamiPaywallAction) -> String {
        switch action {
        case .show_paywall: return "SHOW_PAYWALL"
        case .close_paywall: return "CLOSE_PAYWALL"
        case .restore_purchases: return "RESTORE_PURCHASES"
        case .sign_in: return "SIGN_IN"
        case .buy_sku: return "BUY_SKU"
        case .select_sku: return "SELECT_SKU"
        case .purchase_selected_sku: return "PURCHASE_SELECTED_SKU"
        case .purchase_success: return "PURCHASE_SUCCESS"
        case .purchase_pending: return "PURCHASE_PENDING"
        case .purchase_deferred: return "PURCHASE_DEFERRED"
        case .purchase_failed: return "PURCHASE_FAILED"
        case .purchase_cancelled: return "PURCHASE_CANCELLED"
        case .purchase_unknown: return "PURCHASE_UNKNOWN"
        case .deeplink: return "DEEPLINK"
        case .toggle_change: return "TOGGLE_CHANGE"
        case .page_change: return "PAGE_CHANGE"
        case .slide_change: return "SLIDE_CHANGE"
        case .nami_collapsible_drawer_open: return "COLLAPSIBLE_DRAWER_OPEN"
        case .nami_collapsible_drawer_close: return "COLLAPSIBLE_DRAWER_CLOSE"
        case .video_play: return "VIDEO_STARTED"
        case .video_pause: return "VIDEO_PAUSED"
        case .video_resume: return "VIDEO_RESUMED"
        case .video_end: return "VIDEO_ENDED"
        case .video_change: return "VIDEO_CHANGED"
        case .video_mute: return "VIDEO_MUTED"
        case .video_unmute: return "VIDEO_UNMUTED"
        @unknown default:
            return "UNKNOWN"
        }
    }

    func handlePaywallAction(
        paywallEvent: NamiPaywallEvent
    ) {
        let actionString = mapPaywallActionToJS(paywallEvent.action)

        let errorSting = paywallEvent.purchaseError?.localizedDescription

        let dictionaries = paywallEvent.purchases.map { purchase in RNNamiPurchaseManager.purchaseToPurchaseDict(purchase) }

        var skuDict: [String: Any?] = [:]

        if let sku = paywallEvent.sku {
            skuDict["id"] = sku.id
            skuDict["name"] = sku.name
            skuDict["skuId"] = sku.skuId
            skuDict["type"] = sku.type.description
            skuDict["promoId"] = sku.promoId
        }

        var componentChange: [String: Any?] = [:]

        if let eventComponentChange = paywallEvent.componentChange {
            componentChange["id"] = eventComponentChange.id
            componentChange["name"] = eventComponentChange.name
        }

        var videoMetadata: [String: Any?] = [:]

        if let eventVideoMetadata = paywallEvent.videoMetadata {
            videoMetadata["id"] = eventVideoMetadata.id
            videoMetadata["name"] = eventVideoMetadata.name
            videoMetadata["url"] = eventVideoMetadata.url
            videoMetadata["loopVideo"] = eventVideoMetadata.loopVideo
            videoMetadata["muteByDefault"] = eventVideoMetadata.muteByDefault
            videoMetadata["autoplayVideo"] = eventVideoMetadata.autoplayVideo
            videoMetadata["contentTimecode"] = eventVideoMetadata.contentTimecode
            videoMetadata["contentDuration"] = eventVideoMetadata.contentDuration
        }

        let payload: [String: Any?] = [
            "campaignId": paywallEvent.campaignId,
            "campaignName": paywallEvent.campaignName,
            "campaignType": paywallEvent.campaignType,
            "campaignLabel": paywallEvent.campaignLabel,
            "campaignUrl": paywallEvent.campaignUrl,
            "paywallId": paywallEvent.paywallId,
            "paywallName": paywallEvent.paywallName,
            "segmentId": paywallEvent.segmentId,
            "externalSegmentId": paywallEvent.externalSegmentId,
            "action": actionString,
            "sku": skuDict,
            "purchaseError": errorSting,
            "purchases": dictionaries,
            "deeplinkUrl": paywallEvent.deeplinkUrl,
            "componentChange": componentChange,
            "videoMetadata": videoMetadata,
            "timeSpentOnPaywall": paywallEvent.timeSpentOnPaywall,
        ]

        safeSend(withName: "NamiPaywallEvent", body: payload)
    }

    func handleLaunch(callback: RCTResponseSenderBlock?, success: Bool, error: Error?) {
        guard let callback = callback else {
            print("[handleLaunch] callback was nil — possibly already released.")
            return
        }

        var errorInfo: Any = NSNull()
        if let nsError = error as NSError? {
            errorInfo = [
                "code": nsError.code,
                "domain": nsError.domain,
                "message": nsError.localizedDescription,
            ]
        }

        callback([success, errorInfo])
    }

    @objc(launch:withUrl:context:completion:paywallCompletion:)
    func launch(
        label: String?,
        withUrl: String?,
        context: NSDictionary?,
        callback: @escaping RCTResponseSenderBlock,
        paywallCallback _: @escaping RCTResponseSenderBlock
    ) {
        var paywallLaunchContext: PaywallLaunchContext?

        var productGroups: [String]?
        var customAttributes: [String: Any]?
        var customObject: [String: Any]?

        if let context = context {
            if let contextProductGroups = context["productGroups"] as? [String] {
                productGroups = contextProductGroups
            }
            if let contextCustomAttributes = context["customAttributes"] as? [String: Any] {
                customAttributes = contextCustomAttributes
            }
            if let contextCustomObject = context["customObject"] as? [String: Any] {
                customObject = contextCustomObject
            }
        }

        if productGroups != nil || customAttributes != nil || customObject != nil {
            paywallLaunchContext = PaywallLaunchContext(
                productGroups: productGroups,
                customAttributes: customAttributes,
                customObject: customObject
            )
        }

        // Wrap the callback to ensure it's only called once
        var callbackOnce: RCTResponseSenderBlock? = callback
        let safeCallback: RCTResponseSenderBlock = { args in
            if let cb = callbackOnce {
                cb(args)
                callbackOnce = nil
            } else {
                print("[RNNamiCampaignManager] Warning: callback already called or cleared")
            }
        }

        var launchMethod: (() -> Void)?

        if let urlString = withUrl, let urlObject = URL(string: urlString) {
            launchMethod = {
                NamiCampaignManager.launch(
                    url: urlObject,
                    context: paywallLaunchContext,
                    launchHandler: { [weak self] success, error in
                        self?.handleLaunch(callback: safeCallback, success: success, error: error)
                    },
                    paywallActionHandler: { [weak self] event in
                        self?.handlePaywallAction(paywallEvent: event)
                    }
                )
            }
        } else if let label = label {
            launchMethod = {
                NamiCampaignManager.launch(
                    label: label,
                    context: paywallLaunchContext,
                    launchHandler: { [weak self] success, error in
                        self?.handleLaunch(callback: safeCallback, success: success, error: error)
                    },
                    paywallActionHandler: { [weak self] event in
                        self?.handlePaywallAction(paywallEvent: event)
                    }
                )
            }
        } else {
            print("Neither URL nor label provided calling default launch.")
            launchMethod = {
                NamiCampaignManager.launch(
                    context: paywallLaunchContext,
                    launchHandler: { [weak self] success, error in
                        self?.handleLaunch(callback: safeCallback, success: success, error: error)
                    },
                    paywallActionHandler: { [weak self] event in
                        self?.handlePaywallAction(paywallEvent: event)
                    }
                )
            }
        }

        DispatchQueue.main.async {
            launchMethod?()
        }
    }

    @objc(allCampaigns:rejecter:)
    func allCampaigns(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
        let campaigns = NamiCampaignManager.allCampaigns()
        let dictionaries = campaigns.map { campaign in self.campaignInToDictionary(campaign) }
        resolve(dictionaries)
    }

    @objc(isCampaignAvailable:resolver:rejecter:)
    func isCampaignAvailable(
        campaignSource: String?,
        resolve: @escaping RCTPromiseResolveBlock,
        reject _: @escaping RCTPromiseRejectBlock
    ) {
        var isCampaignAvailable: Bool
        if let source = campaignSource {
            if isURL(string: source), let url = URL(string: source) {
                print("campaignSource is a URL: \(source)")
                isCampaignAvailable = NamiCampaignManager.isCampaignAvailable(url: url)
            } else {
                print("campaignSource is a string: \(source)")
                isCampaignAvailable = NamiCampaignManager.isCampaignAvailable(label: source)
            }
        } else {
            isCampaignAvailable = false
        }
        resolve(isCampaignAvailable)
    }

    @objc(isFlow:withUrl:resolver:rejecter:)
    func isFlow(
        label: String?,
        withUrl: String?,
        resolve: @escaping RCTPromiseResolveBlock,
        reject _: @escaping RCTPromiseRejectBlock
    ) {
        var url: URL?
        if let withUrl = withUrl {
            url = URL(string: withUrl)
        }

        let result = NamiCampaignManager.isFlow(label: label, url: url)
        resolve(result)
    }

    @objc(refresh:rejecter:)
    func refresh(resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
        NamiCampaignManager.refresh { campaigns in
            let dictionaries = campaigns.map { campaign in self.campaignInToDictionary(campaign) }
            resolve(dictionaries)
        }
    }

    @objc(registerAvailableCampaignsHandler)
    func registerForAvailableCampaigns() {
        NamiCampaignManager.registerAvailableCampaignsHandler { availableCampaigns in
            let dictionaries = availableCampaigns.map { campaign in self.campaignInToDictionary(campaign) }
            self.safeSend(withName: "AvailableCampaignsChanged", body: dictionaries)
        }
    }

    @objc(productGroups:withUrl:resolver:rejecter:)
    func productGroups(
        label: String?,
        withUrl: String?,
        resolve: @escaping RCTPromiseResolveBlock,
        reject _: @escaping RCTPromiseRejectBlock
    ) {
        var url: URL?
        if let withUrl = withUrl {
            url = URL(string: withUrl)
        }

        let productGroups = NamiCampaignManager.productGroups(label: label, url: url)
        resolve(productGroups)
    }
}
