import Foundation
import Usercentrics
import UsercentricsUI
import UIKit
import React

#if RCT_NEW_ARCH_ENABLED
#if canImport(ReactCodegen)
// RN ≥ 0.79: getTurboModule: implemented in RNUsercentricsModule+TurboModule.mm (do not import ReactCodegen in Swift — C++ module)
#elseif canImport(React_Codegen)
// RN 0.78
import React_Codegen
#elseif canImport(RNUsercentricsModuleSpec)
// RN ≤ 0.77
import RNUsercentricsModuleSpec
#endif
#endif

@objc(RNUsercentricsModule)
class RNUsercentricsModule: RCTEventEmitter {
    
    var usercentricsManager: UsercentricsManager = UsercentricsManagerImplementation()
    var queue: DispatchQueueManager = DispatchQueue.main
    private var gppSectionChangeSubscription: UsercentricsDisposableEvent<GppSectionChangePayload>?

    @objc override static func moduleName() -> String! {
        return "RNUsercentricsModule"
    }
    
    @objc override static func requiresMainQueueSetup() -> Bool {
        return true
    }

    override func supportedEvents() -> [String]! {
        return [Self.onGppSectionChangeEvent]
    }

    override func startObserving() {
        guard gppSectionChangeSubscription == nil else { return }
        gppSectionChangeSubscription = usercentricsManager.onGppSectionChange { [weak self] payload in
            self?.sendEvent(withName: Self.onGppSectionChangeEvent, body: payload.toDictionary())
        }
    }

    override func stopObserving() {
        gppSectionChangeSubscription?.dispose()
        gppSectionChangeSubscription = nil
    }
    
    @objc func configure(_ dict: NSDictionary) -> Void {
        queue.async { [weak self] in
            guard
                let self = self,
                let userOptions = UsercentricsOptions.initialize(from: dict)
            else { return }

            self.usercentricsManager.configure(options: userOptions)
        }
    }

    @objc func isReady(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        queue.async { [weak self] in
            guard let self = self else { return }

            self.usercentricsManager.isReady { status in
                resolve(status.toDictionary())
            } onFailure: { error in
                reject("usercentrics_reactNative_isReady_error", error.localizedDescription, error)
            }
        }
    }
    
    @objc func showFirstLayer(_ dict: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        queue.async { [weak self] in
            guard
                let self = self
            else {
                reject("usercentrics_reactNative_showFirstLayer_error", RNUsercentricsModuleError.invalidData.localizedDescription, RNUsercentricsModuleError.invalidData)
                return
            }

            self.usercentricsManager.showFirstLayer(bannerSettings: BannerSettings(from: dict)) { response in
                resolve(response.toDictionary())
            }
        }
    }
    
    @objc func showSecondLayer(_ dict: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        queue.async { [weak self] in
            guard
                let self = self
            else {
                reject("usercentrics_reactNative_showFirstLayer_error", RNUsercentricsModuleError.invalidData.localizedDescription, RNUsercentricsModuleError.invalidData)
                return
            }

            self.usercentricsManager.showSecondLayer(bannerSettings: BannerSettings(from: dict)) { response in
                resolve(response.toDictionary())
            }
        }
    }
    
    @objc func setCMPId(_ id: Double) -> Void {
        usercentricsManager.setCMPId(id: Int32(id))
    }

    @objc func setABTestingVariant(_ variant: String) -> Void {
        usercentricsManager.setABTestingVariant(variant: variant)
    }

    @objc func restoreUserSession(_ controllerId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        usercentricsManager.restoreUserSession(controllerId: controllerId) { status in
            resolve(status.toDictionary())
        } onFailure: { error in
            reject("usercentrics_reactNative_restoreUserSession_error", error.localizedDescription, error)
        }
    }
    
    @objc func getControllerId(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getControllerId())
    }
    
    @objc func getConsents(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getConsents().toListOfDictionary())
    }
    
    @objc func getCMPData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getCMPData().toDictionary())
    }
    
    @objc func getTCFData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        usercentricsManager.getTCFData { tcfData in
            resolve(tcfData.toDictionary())
        }
    }
    
    @objc func getUserSessionData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getUserSessionData())
    }
    
    @objc func getUSPData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getUSPData().toDictionary())
    }

    @objc func getGPPData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getGPPData().toDictionary())
    }

    @objc func getGPPString(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getGPPString())
    }

    @objc func setGPPConsent(_ sectionName: String, fieldName: String, value: NSDictionary) -> Void {
        let unwrapped = value["value"] ?? NSNull()
        usercentricsManager.setGPPConsent(sectionName: sectionName, fieldName: fieldName, value: unwrapped)
    }

    @objc func getABTestingVariant(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getABTestingVariant())
    }

    @objc func getDpsMetadata(_ templateId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        let metadata = usercentricsManager.getDpsMetadata(templateId: templateId)
        resolve(metadata as NSDictionary?)
    }

    @objc func getAdditionalConsentModeData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        resolve(usercentricsManager.getAdditionalConsentModeData().toDictionary())
    }

    @objc func changeLanguage(_ language: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        usercentricsManager.changeLanguage(language: language) {
            resolve(Void.self)
        } onFailure: { error in
            reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
        }
    }
    
    @objc func acceptAllForTCF(_ fromLayer: Double,
                               consentType: Double,
                               resolve: @escaping RCTPromiseResolveBlock,
                               reject: @escaping RCTPromiseRejectBlock) -> Void {
        let services = usercentricsManager.acceptAllForTCF(fromLayer: TCFDecisionUILayer.initialize(from: Int(fromLayer)),
                                                           consentType: UsercentricsConsentType.initialize(from: Int(consentType)))
        resolve(services.toListOfDictionary())
    }
    
    @objc func acceptAll(_ consentType: Double,
                         resolve: @escaping RCTPromiseResolveBlock,
                         reject: @escaping RCTPromiseRejectBlock) -> Void {
        let services = usercentricsManager.acceptAll(consentType: UsercentricsConsentType.initialize(from: Int(consentType)))
        resolve(services.toListOfDictionary())
    }
    
    @objc func denyAllForTCF(_ fromLayer: Double,
                             consentType: Double,
                             unsavedPurposeLIDecisions: [NSDictionary],
                             unsavedVendorLIDecisions: [NSDictionary],
                             resolve: @escaping RCTPromiseResolveBlock,
                             reject: @escaping RCTPromiseRejectBlock) -> Void {
        let services = usercentricsManager.denyAllForTCF(
            fromLayer: .initialize(from: Int(fromLayer)),
            consentType: .initialize(from: Int(consentType)),
            unsavedPurposeLIDecisions: extractLIDecisionsMap(unsavedPurposeLIDecisions),
            unsavedVendorLIDecisions: extractLIDecisionsMap(unsavedVendorLIDecisions)
        )
        resolve(services.toListOfDictionary())
    }

    private func extractLIDecisionsMap(_ decisions: [NSDictionary]) -> [KotlinInt: KotlinBoolean]? {
        guard !decisions.isEmpty else { return nil }
        return decisions.reduce(into: [:]) { result, dict in
            if let id = dict["id"] as? Int,
               let consent = dict["legitimateInterestConsent"] as? Bool {
                result[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent)
            }
        }
    }
    
    @objc func denyAll(_ consentType: Double,
                       resolve: @escaping RCTPromiseResolveBlock,
                       reject: @escaping RCTPromiseRejectBlock) -> Void {
        let services = usercentricsManager.denyAll(consentType: .initialize(from: Int(consentType)))
        resolve(services.toListOfDictionary())
    }
    
    @objc func saveDecisionsForTCF(_ tcfDecisions: NSDictionary,
                                   fromLayer: Double,
                                   serviceDecisions: [NSDictionary],
                                   consentType: Double,
                                   resolve: @escaping RCTPromiseResolveBlock,
                                   reject: @escaping RCTPromiseRejectBlock) -> Void {
        
        let services = usercentricsManager.saveDecisionsForTCF(
            tcfDecisions: TCFUserDecisions(from: tcfDecisions),
            fromLayer: .initialize(from: Int(fromLayer)),
            serviceDecisions: serviceDecisions.compactMap { UserDecision(from: $0) },
            consentType: .initialize(from: Int(consentType)))
        resolve(services.toListOfDictionary())
        
    }
    
    @objc func saveDecisions(_ decisions: [NSDictionary],
                             consentType: Double,
                             resolve: @escaping RCTPromiseResolveBlock,
                             reject: @escaping RCTPromiseRejectBlock) -> Void {
        let services = usercentricsManager.saveDecisions(decisions: decisions.compactMap { UserDecision.init(from: $0) }, consentType: .initialize(from: Int(consentType)))
        resolve(services.toListOfDictionary())
    }
    
    @objc func saveOptOutForCCPA(_ isOptedOut: Bool,
                                 consentType: Double,
                                 resolve: @escaping RCTPromiseResolveBlock,
                                 reject: @escaping RCTPromiseRejectBlock) -> Void {
        let services = usercentricsManager.saveOptOutForCCPA(isOptedOut: isOptedOut, consentType: .initialize(from: Int(consentType)))
        resolve(services.toListOfDictionary())
    }

    @objc func track(_ event: Double) -> Void {
        guard let usercentricsAnalyticsEventType = UsercentricsAnalyticsEventType.initialize(from: Int(event)) else { return }
        usercentricsManager.track(event: usercentricsAnalyticsEventType)
    }
    
    @objc func clearUserSession(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
        usercentricsManager.clearUserSession { status in
            resolve(status.toDictionary())
        } onError: { error in
            reject("usercentrics_reactNative_clearUserSession_error", error.localizedDescription, error)
        }
    }

    private static let onGppSectionChangeEvent = "onGppSectionChange"
}

// MARK: - RCTBridgeModule & TurboModule Conformance
#if RCT_NEW_ARCH_ENABLED
#if canImport(React_Codegen)
// RN 0.78: codegen-generated protocol
extension RNUsercentricsModule: NativeUsercentricsModuleSpec {
    func getTurboModule(jsInvoker: RCTJSInvoker) -> Any {
        return self
    }
}
#elseif canImport(RNUsercentricsModuleSpec)
// RN ≤ 0.77
extension RNUsercentricsModule: NativeUsercentricsSpec {
    func getTurboModule(jsInvoker: RCTJSInvoker) -> Any {
        return self
    }
}
#endif
#endif
