import Foundation
import BigCrunchAds
#if canImport(React)
import React
#endif

// MARK: - Interstitial Delegate

class InterstitialDelegate: NSObject, BigCrunchInterstitialDelegate {
    weak var module: BigCrunchAdsModule?
    let placementId: String

    init(module: BigCrunchAdsModule, placementId: String) {
        self.module = module
        self.placementId = placementId
    }

    func interstitialDidShow(placementId: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchInterstitialAdOpened", body: [
                "placementId": placementId
            ])
            // Also emit impression event
            module?.sendEvent(withName: "BigCrunchInterstitialAdImpression", body: [
                "placementId": placementId
            ])
        }
    }

    func interstitialDidDismiss(placementId: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchInterstitialAdClosed", body: [
                "placementId": placementId
            ])
        }
        // Clear from cache
        module?.interstitialCache.removeValue(forKey: placementId)
    }

    func interstitialDidClick(placementId: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchInterstitialAdClicked", body: [
                "placementId": placementId
            ])
        }
    }

    func interstitialDidFail(placementId: String, error: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchInterstitialAdFailedToShow", body: [
                "placementId": placementId,
                "errorCode": "SHOW_FAILED",
                "errorMessage": error
            ])
        }
    }
}

// MARK: - Rewarded Delegate

class RewardedDelegate: NSObject, BigCrunchRewardedDelegate {
    weak var module: BigCrunchAdsModule?
    let placementId: String

    init(module: BigCrunchAdsModule, placementId: String) {
        self.module = module
        self.placementId = placementId
    }

    func rewardedDidShow(placementId: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchRewardedAdOpened", body: [
                "placementId": placementId
            ])
            // Also emit impression event
            module?.sendEvent(withName: "BigCrunchRewardedAdImpression", body: [
                "placementId": placementId
            ])
        }
    }

    func rewardedDidDismiss(placementId: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchRewardedAdClosed", body: [
                "placementId": placementId
            ])
        }
        // Clear from cache and delegate
        module?.rewardedCache.removeValue(forKey: placementId)
        module?.rewardedDelegates.removeValue(forKey: placementId)
    }

    func rewardedDidClick(placementId: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchRewardedAdClicked", body: [
                "placementId": placementId
            ])
        }
    }

    func rewardedDidEarnReward(placementId: String, type: String, amount: Int) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchRewardedAdEarned", body: [
                "placementId": placementId,
                "type": type,
                "amount": amount
            ])
        }
    }

    func rewardedDidFail(placementId: String, error: String) {
        if module?.hasListeners == true {
            module?.sendEvent(withName: "BigCrunchRewardedAdFailedToShow", body: [
                "placementId": placementId,
                "errorCode": "SHOW_FAILED",
                "errorMessage": error
            ])
        }
        // Clean up delegate on failure
        module?.rewardedDelegates.removeValue(forKey: placementId)
    }
}

// MARK: - Initialization Callback Bridge

class InitCallbackBridge: NSObject, BigCrunchInitializationCallback {
    private let onSuccess: () -> Void
    private let onFailure: (String) -> Void

    init(onSuccess: @escaping () -> Void, onFailure: @escaping (String) -> Void) {
        self.onSuccess = onSuccess
        self.onFailure = onFailure
    }

    func onInitialized() {
        NSLog("[BigCrunchRNBridge] callback onInitialized fired, resolving promise")
        onSuccess()
    }

    func onInitializationFailed(error: String) {
        NSLog("[BigCrunchRNBridge] callback onInitializationFailed: %@", error)
        onFailure(error)
    }
}

@objc(BigCrunchAdsModule)
class BigCrunchAdsModule: RCTEventEmitter {

    // Made internal for delegate access
    var hasListeners = false
    var interstitialCache: [String: Any] = [:]
    var rewardedCache: [String: Any] = [:]
    private var interstitialDelegates: [String: InterstitialDelegate] = [:]
    var rewardedDelegates: [String: RewardedDelegate] = [:]
    private var initCallbackBridge: InitCallbackBridge?

    override static func moduleName() -> String! {
        return "BigCrunchAdsModule"
    }

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

    override func supportedEvents() -> [String]! {
        return [
            // Banner events - matching TypeScript NativeEventNames
            "BigCrunchBannerAdLoaded",
            "BigCrunchBannerAdFailedToLoad",
            "BigCrunchBannerAdImpression",
            "BigCrunchBannerAdClicked",
            "BigCrunchBannerAdOpened",
            "BigCrunchBannerAdClosed",
            "BigCrunchBannerAdRevenue",
            "BigCrunchBannerAdViewable",

            // Interstitial events - matching TypeScript NativeEventNames
            "BigCrunchInterstitialAdLoaded",
            "BigCrunchInterstitialAdFailedToLoad",
            "BigCrunchInterstitialAdOpened",
            "BigCrunchInterstitialAdImpression",
            "BigCrunchInterstitialAdClicked",
            "BigCrunchInterstitialAdClosed",
            "BigCrunchInterstitialAdFailedToShow",
            "BigCrunchInterstitialAdRevenue",

            // Rewarded events - matching TypeScript NativeEventNames
            "BigCrunchRewardedAdLoaded",
            "BigCrunchRewardedAdFailedToLoad",
            "BigCrunchRewardedAdOpened",
            "BigCrunchRewardedAdImpression",
            "BigCrunchRewardedAdClicked",
            "BigCrunchRewardedAdClosed",
            "BigCrunchRewardedAdEarned",
            "BigCrunchRewardedAdFailedToShow",
            "BigCrunchRewardedAdRevenue",

            // Configuration events
            "BigCrunchConfigUpdated",
            "BigCrunchConfigFailed",

            // Session events
            "BigCrunchSessionStarted",
            "BigCrunchSessionEnded"
        ]
    }

    override func startObserving() {
        hasListeners = true
    }

    override func stopObserving() {
        hasListeners = false
    }

    // MARK: - Initialization

    @objc(initialize:resolver:rejecter:)
    func initialize(options: NSDictionary,
                   resolver: @escaping RCTPromiseResolveBlock,
                   rejecter: @escaping RCTPromiseRejectBlock) {
        guard let propertyId = options["propertyId"] as? String else {
            rejecter("INIT_ERROR", "propertyId is required", nil)
            return
        }

        let environment = options["environment"] as? String ?? "production"
        let debug = options["debug"] as? Bool ?? false
        let useMockConfig = options["useMockConfig"] as? Bool ?? false
        let useTestAds = options["useTestAds"] as? Bool ?? false

        DispatchQueue.main.async {
            // Map environment strings to SDK enum - "sandbox" maps to .staging
            let env: BigCrunchEnv = environment == "sandbox" ? .staging : .prod

            NSLog("[BigCrunchRNBridge] initialize() called: propertyId=%@ env=%@ mock=%d testAds=%d", propertyId, environment, useMockConfig ? 1 : 0, useTestAds ? 1 : 0)

            // Use callback to wait for config to load before resolving the promise
            // This ensures placements are available when the app starts loading ads
            let callbackBridge = InitCallbackBridge(
                onSuccess: { [weak self] in
                    NSLog("[BigCrunchRNBridge] init complete, setting isSDKInitialized=true, resolving promise")
                    BigCrunchAdsModule.isSDKInitialized = true
                    if self?.hasListeners == true {
                        self?.sendEvent(withName: "BigCrunchConfigUpdated", body: [:])
                        self?.sendEvent(withName: "BigCrunchSessionStarted", body: [:])
                    }
                    resolver(nil)
                },
                onFailure: { [weak self] error in
                    NSLog("[BigCrunchRNBridge] init failed: %@, rejecting promise", error)
                    if self?.hasListeners == true {
                        self?.sendEvent(withName: "BigCrunchConfigFailed", body: ["errorMessage": error])
                    }
                    rejecter("INIT_ERROR", "Failed to load configuration: \(error)", nil)
                }
            )
            // Store strong reference so it isn't deallocated before callback fires
            self.initCallbackBridge = callbackBridge

            BigCrunchAds.initialize(
                propertyId: propertyId,
                env: env,
                useMockConfig: useMockConfig,
                useTestAds: useTestAds,
                callback: callbackBridge
            )

            if debug {
                BigCrunchAds.setDebugMode(true)
            }

            NSLog("[BigCrunchRNBridge] native initialize() returned, waiting for callback...")
        }
    }

    private static var isSDKInitialized = false

    @objc(isInitialized:rejecter:)
    func isInitialized(resolver: RCTPromiseResolveBlock,
                      rejecter: RCTPromiseRejectBlock) {
        // iOS SDK doesn't expose isInitialized() publicly yet
        // Return true if we've called initialize (tracked locally)
        resolver(BigCrunchAdsModule.isSDKInitialized)
    }

    // MARK: - Screen Tracking

    @objc(trackScreenView:options:resolver:rejecter:)
    func trackScreenView(screenName: String,
                        options: NSDictionary?,
                        resolver: @escaping RCTPromiseResolveBlock,
                        rejecter: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            var screenViewOptions: ScreenViewOptions? = nil

            if let opts = options as? [String: Any] {
                var pageMeta: PageMetaData? = nil
                if let metaDict = opts["pageMeta"] as? [String: String] {
                    pageMeta = PageMetaData(
                        url: metaDict["url"],
                        author: metaDict["author"],
                        title: metaDict["title"],
                        thumbnailUrl: metaDict["thumbnailUrl"],
                        articleSection: metaDict["articleSection"],
                        keywords: metaDict["keywords"],
                        dateCreated: metaDict["dateCreated"],
                        dateModified: metaDict["dateModified"],
                        datePublished: metaDict["datePublished"]
                    )
                }

                screenViewOptions = ScreenViewOptions(
                    pageUrl: opts["pageUrl"] as? String,
                    pageMeta: pageMeta,
                    customDimensions: opts["customDimensions"] as? [String: String]
                )
            }

            BigCrunchAds.trackScreen(screenName, options: screenViewOptions)
            resolver(nil)
        }
    }

    // MARK: - Configuration

    @objc(getAppConfig:rejecter:)
    func getAppConfig(resolver: @escaping RCTPromiseResolveBlock,
                     rejecter: @escaping RCTPromiseRejectBlock) {
        if let config = BigCrunchAds.getAppConfig() {
            // Convert AppConfig to dictionary
            let configDict: [String: Any] = [
                "propertyId": config.propertyId,
                "appName": config.appName,
                "gamNetworkCode": config.gamNetworkCode,
                "s2s": [
                    "enabled": config.s2s.enabled,
                    "serverUrl": config.s2s.serverUrl,
                    "timeoutMs": config.s2s.timeoutMs
                ],
                "placements": config.placements.map { placement in
                    var placementDict: [String: Any] = [
                        "placementId": placement.placementId,
                        "format": placement.format,
                        "gamAdUnit": placement.gamAdUnit
                    ]
                    if let sizes = placement.sizes {
                        placementDict["sizes"] = sizes.map { ["width": $0.width, "height": $0.height] }
                    }
                    return placementDict
                }
            ]
            resolver(configDict)
        } else {
            resolver(nil)
        }
    }

    @objc(refreshConfig:rejecter:)
    func refreshConfig(resolver: @escaping RCTPromiseResolveBlock,
                      rejecter: @escaping RCTPromiseRejectBlock) {
        Task { [weak self] in
            await BigCrunchAds.refreshConfig()
            DispatchQueue.main.async {
                if self?.hasListeners == true {
                    self?.sendEvent(withName: "BigCrunchConfigUpdated", body: [:])
                }
                resolver(nil)
            }
        }
    }

    // MARK: - Interstitial Ads

    @objc(loadInterstitial:resolver:rejecter:)
    func loadInterstitial(options: NSDictionary,
                         resolver: @escaping RCTPromiseResolveBlock,
                         rejecter: @escaping RCTPromiseRejectBlock) {
        guard let placementId = options["placementId"] as? String else {
            rejecter("LOAD_ERROR", "placementId is required", nil)
            return
        }

        let customTargeting = options["customTargeting"] as? [String: String]

        // Use preload with completion handler to emit events
        BigCrunchInterstitial.preload(placementId: placementId) { [weak self] result in
            switch result {
            case .success:
                // Store in cache and emit loaded event
                self?.interstitialCache[placementId] = true

                if self?.hasListeners == true {
                    self?.sendEvent(withName: "BigCrunchInterstitialAdLoaded", body: [
                        "placementId": placementId
                    ])
                }
                resolver(nil)

            case .failure(let error):
                // Emit failed event
                if self?.hasListeners == true {
                    self?.sendEvent(withName: "BigCrunchInterstitialAdFailedToLoad", body: [
                        "placementId": placementId,
                        "errorCode": "LOAD_FAILED",
                        "errorMessage": error.localizedDescription
                    ])
                }
                rejecter("LOAD_ERROR", "Failed to load interstitial: \(error.localizedDescription)", error)
            }
        }
    }

    @objc(showInterstitial:resolver:rejecter:)
    func showInterstitial(placementId: String,
                         resolver: @escaping RCTPromiseResolveBlock,
                         rejecter: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
                rejecter("NO_VIEWCONTROLLER", "No root view controller available", nil)
                return
            }

            // Create and store delegate
            let delegate = InterstitialDelegate(module: self, placementId: placementId)
            self.interstitialDelegates[placementId] = delegate

            // Show with delegate
            let success = BigCrunchInterstitial.show(
                from: rootViewController,
                placementId: placementId,
                delegate: delegate
            )

            if success {
                resolver(nil)
            } else {
                // Clean up delegate if show failed immediately
                self.interstitialDelegates.removeValue(forKey: placementId)
                rejecter("SHOW_ERROR", "Failed to show interstitial: Ad not loaded", nil)
            }
        }
    }

    @objc(isInterstitialLoaded:resolver:rejecter:)
    func isInterstitialLoaded(placementId: String,
                             resolver: RCTPromiseResolveBlock,
                             rejecter: RCTPromiseRejectBlock) {
        resolver(BigCrunchInterstitial.isReady(placementId: placementId))
    }

    @objc(destroyInterstitial:resolver:rejecter:)
    func destroyInterstitial(placementId: String,
                            resolver: RCTPromiseResolveBlock,
                            rejecter: RCTPromiseRejectBlock) {
        // iOS SDK uses clearCache() instead of per-placement destroy
        // Just clear our local cache for now
        interstitialCache.removeValue(forKey: placementId)
        resolver(nil)
    }

    // MARK: - Rewarded Ads

    @objc(loadRewarded:resolver:rejecter:)
    func loadRewarded(options: NSDictionary,
                     resolver: @escaping RCTPromiseResolveBlock,
                     rejecter: @escaping RCTPromiseRejectBlock) {
        guard let placementId = options["placementId"] as? String else {
            rejecter("LOAD_ERROR", "placementId is required", nil)
            return
        }

        // customTargeting can be used in future for custom S2S targeting
        // let customTargeting = options["customTargeting"] as? [String: String]

        BigCrunchRewarded.preload(placementId: placementId) { [weak self] result in
            switch result {
            case .success:
                // Mark as loaded in our cache
                self?.rewardedCache[placementId] = true
                if self?.hasListeners == true {
                    self?.sendEvent(withName: "BigCrunchRewardedAdLoaded", body: [
                        "placementId": placementId
                    ])
                }
                resolver(nil)
            case .failure(let error):
                if self?.hasListeners == true {
                    self?.sendEvent(withName: "BigCrunchRewardedAdFailedToLoad", body: [
                        "placementId": placementId,
                        "errorCode": "LOAD_FAILED",
                        "errorMessage": error.localizedDescription
                    ])
                }
                rejecter("LOAD_ERROR", error.localizedDescription, error)
            }
        }
    }

    @objc(showRewarded:resolver:rejecter:)
    func showRewarded(placementId: String,
                     resolver: @escaping RCTPromiseResolveBlock,
                     rejecter: @escaping RCTPromiseRejectBlock) {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                rejecter("SHOW_ERROR", "Module deallocated", nil)
                return
            }

            guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
                rejecter("SHOW_ERROR", "No root view controller available", nil)
                return
            }

            // Create and store delegate
            let delegate = RewardedDelegate(module: self, placementId: placementId)
            self.rewardedDelegates[placementId] = delegate

            // Show with delegate
            let success = BigCrunchRewarded.show(
                from: rootViewController,
                placementId: placementId,
                delegate: delegate
            )

            if success {
                resolver(nil)
            } else {
                // Clean up delegate if show failed immediately
                self.rewardedDelegates.removeValue(forKey: placementId)
                rejecter("SHOW_ERROR", "Failed to show rewarded: Ad not loaded", nil)
            }
        }
    }

    @objc(isRewardedLoaded:resolver:rejecter:)
    func isRewardedLoaded(placementId: String,
                         resolver: RCTPromiseResolveBlock,
                         rejecter: RCTPromiseRejectBlock) {
        resolver(BigCrunchRewarded.isReady(placementId: placementId))
    }

    @objc(destroyRewarded:resolver:rejecter:)
    func destroyRewarded(placementId: String,
                        resolver: RCTPromiseResolveBlock,
                        rejecter: RCTPromiseRejectBlock) {
        // iOS SDK doesn't have per-placement destroy for rewarded
        // Just clear our local cache for now
        rewardedCache.removeValue(forKey: placementId)
        resolver(nil)
    }

    // MARK: - Session Management

    @objc(getSessionInfo:rejecter:)
    func getSessionInfo(resolver: @escaping RCTPromiseResolveBlock,
                       rejecter: @escaping RCTPromiseRejectBlock) {
        let info = BigCrunchAds.getSessionInfo()
        let sessionInfo: [String: Any] = [
            "sessionId": info.sessionId,
            "userId": info.userId,
            "startTime": info.startTime,
            "isNewUser": info.isNewUser,
            "sessionDepth": info.sessionDepth,
            "screenViewCount": info.screenViewCount,
            "adRequestCount": info.adRequestCount,
            "adImpressionCount": info.adImpressionCount,
            "totalRevenueMicros": info.totalRevenueMicros
        ]
        resolver(sessionInfo)
    }

    @objc(startNewSession:rejecter:)
    func startNewSession(resolver: @escaping RCTPromiseResolveBlock,
                        rejecter: @escaping RCTPromiseRejectBlock) {
        BigCrunchAds.startNewSession()
        if hasListeners {
            sendEvent(withName: "BigCrunchSessionStarted", body: [:])
        }
        resolver(nil)
    }

    // MARK: - UTM Attribution

    @objc(setUTMParameters:resolver:rejecter:)
    func setUTMParameters(params: NSDictionary,
                          resolver: @escaping RCTPromiseResolveBlock,
                          rejecter: @escaping RCTPromiseRejectBlock) {
        BigCrunchAds.setUTMParameters(
            source: params["source"] as? String,
            medium: params["medium"] as? String,
            campaign: params["campaign"] as? String,
            term: params["term"] as? String,
            content: params["content"] as? String
        )
        resolver(nil)
    }

    @objc(clearUTMParameters:rejecter:)
    func clearUTMParameters(resolver: @escaping RCTPromiseResolveBlock,
                            rejecter: @escaping RCTPromiseRejectBlock) {
        BigCrunchAds.clearUTMParameters()
        resolver(nil)
    }

    // MARK: - Device Context

    @objc(getDeviceContext:rejecter:)
    func getDeviceContext(resolver: @escaping RCTPromiseResolveBlock,
                         rejecter: @escaping RCTPromiseRejectBlock) {
        let data = BigCrunchAds.getDeviceData()
        let deviceContext: [String: Any] = [
            "os": "iOS",
            "osVersion": data.osVersion,
            "deviceModel": data.deviceModel,
            "screenWidth": data.screenWidth,
            "screenHeight": data.screenHeight,
            "screenDensity": UIScreen.main.scale,
            "appVersion": data.appVersion,
            "sdkVersion": "0.1.0",
            "locale": "\(data.language)_\(data.country)",
            "timezone": TimeZone.current.identifier,
            "isTablet": data.isTablet,
            "deviceId": data.deviceId
        ]
        resolver(deviceContext)
    }

    // MARK: - Privacy

    @objc(setGdprConsent:resolver:rejecter:)
    func setGdprConsent(consent: String,
                       resolver: RCTPromiseResolveBlock,
                       rejecter: RCTPromiseRejectBlock) {
        BigCrunchAds.setGdprConsent(consent)
        resolver(nil)
    }

    @objc(setCcpaString:resolver:rejecter:)
    func setCcpaString(ccpaString: String,
                      resolver: RCTPromiseResolveBlock,
                      rejecter: RCTPromiseRejectBlock) {
        BigCrunchAds.setCcpaString(ccpaString)
        resolver(nil)
    }

    @objc(setCoppaCompliant:resolver:rejecter:)
    func setCoppaCompliant(isCompliant: Bool,
                          resolver: RCTPromiseResolveBlock,
                          rejecter: RCTPromiseRejectBlock) {
        BigCrunchAds.setCoppaCompliant(isCompliant)
        resolver(nil)
    }

    // MARK: - Account Type

    @objc(setAccountType:resolver:rejecter:)
    func setAccountType(accountType: String,
                       resolver: RCTPromiseResolveBlock,
                       rejecter: RCTPromiseRejectBlock) {
        BigCrunchAds.setAccountType(accountType)
        resolver(nil)
    }

    // MARK: - Debug & Testing

    @objc(setDebugMode:resolver:rejecter:)
    func setDebugMode(enabled: Bool,
                     resolver: RCTPromiseResolveBlock,
                     rejecter: RCTPromiseRejectBlock) {
        BigCrunchAds.setDebugMode(enabled)
        resolver(nil)
    }

    @objc(addTestDevice:resolver:rejecter:)
    func addTestDevice(deviceId: String,
                      resolver: RCTPromiseResolveBlock,
                      rejecter: RCTPromiseRejectBlock) {
        BigCrunchAds.addTestDevice(deviceId)
        resolver(nil)
    }

    @objc(removeTestDevice:resolver:rejecter:)
    func removeTestDevice(deviceId: String,
                         resolver: RCTPromiseResolveBlock,
                         rejecter: RCTPromiseRejectBlock) {
        BigCrunchAds.removeTestDevice(deviceId)
        resolver(nil)
    }

    @objc(getTestDevices:rejecter:)
    func getTestDevices(resolver: RCTPromiseResolveBlock,
                       rejecter: RCTPromiseRejectBlock) {
        resolver(BigCrunchAds.getTestDevices())
    }

    // MARK: - Helper Methods
    // Note: convertAppConfigToDictionary and convertPlacementConfigToDictionary
    // are kept for future use when iOS SDK exposes getAppConfig
}