import BluxClient

@objc(Blux)
class Blux: RCTEventEmitter {
    private let NOTIFICATION_CLICKED_EVENT_KEY = "notification-clicked"
    private let NOTIFICATION_FOREGROUND_WILL_DISPLAY_EVENT_KEY = "notification-foreground-will-display"
    private let INAPP_CLICKED_EVENT_KEY = "inapp-clicked"
    private let INAPP_CUSTOM_ACTION_EVENT_KEY = "inapp-custom-action"

    private var inAppCustomActionUnsubscribe: (() -> Void)?

    private var pendingNotificationForegroundWillDisplayEvents: [String: NotificationReceivedEvent] = [:]

    @objc(initialize:withBluxApiKey:withRequestPermissionOnLaunch:withCustomDeviceId:withResolver:withRejecter:)
    func initialize(bluxApplicationId: String, bluxAPIKey: String, requestPermissionOnLaunch: Bool, customDeviceId: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        DispatchQueue.main.async {
            let launchOptions = self.bridge.launchOptions as? [UIApplication.LaunchOptionsKey: Any]
            BluxClient.initialize(launchOptions, bluxApplicationId: bluxApplicationId, bluxAPIKey: bluxAPIKey, requestPermissionOnLaunch: requestPermissionOnLaunch, customDeviceId: customDeviceId) { _ in
                resolve(nil)
            }
        }
    }

    func convertLogLevelToEnum(level: String) -> LogLevel? {
        switch level.lowercased() {
        case "error":
            return LogLevel.error
        case "verbose":
            return LogLevel.verbose
        default:
            return nil
        }
    }

    @objc(setLogLevel:withResolver:withRejecter:)
    func setLogLevel(level: String, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
        let levelAsEnum = convertLogLevelToEnum(level: level)
        if let validLevel = levelAsEnum {
            BluxClient.setLogLevel(level: validLevel)
            resolve(nil)
        } else {
            reject("INVALID_LOG_LEVEL", "Invalid log level: \(level)", NSError(domain: "", code: 400, userInfo: [NSLocalizedDescriptionKey: "Invalid log level: \(level)"]))
        }
    }

    @objc(signIn:withResolver:withRejecter:)
    func signIn(userId: String, resolver resolve: RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        BluxClient.signIn(userId: userId)
        resolve(nil)
    }

    @objc(signOut:withRejecter:)
    func signOut(resolver resolve: RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        BluxClient.signOut()
        resolve(nil)
    }

    @objc(setUserProperties:withResolver:withRejecter:)
    func setUserProperties(userProperties: [String: Any], resolver resolve: RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        BluxClient.setUserPropertiesData(userProperties: userProperties)
        resolve(nil)
    }

    @objc(setCustomUserProperties:withResolver:withRejecter:)
    func setCustomUserProperties(
        customUserProperties: [String: Any],
        resolver resolve: @escaping RCTPromiseResolveBlock,
        rejecter reject: @escaping RCTPromiseRejectBlock
    ) {
        do {
            try BluxClient.setCustomUserProperties(customUserProperties: customUserProperties)
            resolve(nil)
        } catch {
            reject("SET_CUSTOM_USER_PROPERTIES_ERROR", error.localizedDescription, error)
        }
    }

    @objc(sendRequest:withResolver:withRejecter:)
    func sendRequest(events: NSArray, resolver resolve: RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        let eventsCodable = events.compactMap { dict -> Event? in
            guard let dict = dict as? NSDictionary,
                  let eventType = dict["event_type"] as? String,
                  let captured_at = dict["captured_at"] as? String
            else {
                return nil
            }

            var event = try? Event(eventType: eventType)
            event?.capturedAt = captured_at

            // Handle event_properties
            if let eventPropertiesDict = dict["event_properties"] as? [String: Any] {
                do {
                    // 딕셔너리를 JSON Data로 직렬화
                    let data = try JSONSerialization.data(withJSONObject: eventPropertiesDict, options: [])

                    // Codable 객체로 디코딩
                    let decoder = JSONDecoder()
                    let eventProperties = try decoder.decode(EventProperties.self, from: data)

                    Logger.verbose("eventPropertiesDict: \(eventProperties)")
                    event = event?.setEventProperties(eventProperties)
                } catch {
                    Logger.error("Failed to decode EventProperties: \(error)")
                }
            }

            // Handle custom_event_properties
            if let customEventPropertiesDict = dict["custom_event_properties"] as? NSDictionary {
                var customEventProperties: [String: CustomEventValue] = [:]
                for (key, value) in customEventPropertiesDict {
                    if let keyString = key as? String, let convertedValue = CustomEventValue.fromAny(value) {
                        customEventProperties[keyString] = convertedValue
                    }
                }

                event = event?.setCustomEventProperties(customEventProperties)
            }

            // Handle internal_event_properties (unsupported types: key dropped, not sent)
            if let internalEventPropertiesDict = dict["internal_event_properties"] as? NSDictionary {
                var internalEventProperties: [String: CustomEventValue] = [:]
                for (key, value) in internalEventPropertiesDict {
                    if let keyString = key as? String, let convertedValue = CustomEventValue.fromAny(value) {
                        internalEventProperties[keyString] = convertedValue
                    }
                }
                event = event?.setInternalEventProperties(internalEventProperties)
            }

            return event
        }
        BluxClient.sendRequestData(eventsCodable)
        resolve(nil)
    }

    @objc(setNotificationUrlOpenOptions:withResolver:withRejecter:)
    func setNotificationUrlOpenOptions(options: [String: Any], resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
        let raw = options["httpUrlOpenTarget"] as? String ?? "internalWebView"
        guard let target = Blux.parseHttpUrlOpenTarget(raw) else {
            reject("INVALID_ARGUMENT", "Invalid httpUrlOpenTarget: \(raw)", nil)
            return
        }
        BluxClient.setNotificationUrlOpenOptions(NotificationUrlOpenOptions(httpUrlOpenTarget: target))
        resolve(nil)
    }

    @objc(startNotificationForegroundWillDisplayHandler:withRejecter:)
    func startNotificationForegroundWillDisplayHandler(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        BluxClient.setNotificationForegroundWillDisplayHandler { [weak self] event in
            guard let self = self else { return }
            let id = event.notification.id
            // display()/suppress()/fallback timer 어느 경로로 completion이 끝나든 onComplete이 호출되므로 여기서 dict 정리.
            event.onComplete = { [weak self] in
                self?.pendingNotificationForegroundWillDisplayEvents.removeValue(forKey: id)
            }
            self.pendingNotificationForegroundWillDisplayEvents[id] = event
            self.sendEvent(withName: self.NOTIFICATION_FOREGROUND_WILL_DISPLAY_EVENT_KEY, body: ["notification": event.notification.toDictionary()])
        }
        resolve(nil)
    }

    @objc(displayNotification:withResolver:withRejecter:)
    func displayNotification(notification: [String: Any], resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
        guard let id = notification["id"] as? String else {
            reject("INVALID_ARGUMENT", "notification.id is required", nil)
            return
        }
        guard let pending = pendingNotificationForegroundWillDisplayEvents.removeValue(forKey: id) else {
            reject("DISPLAY_NOTIFICATION_FAILED", "No pending event for id: \(id)", nil)
            return
        }
        pending.display()
        resolve(nil)
    }

    @objc(startNotificationClickedHandler:withRejecter:)
    func startNotificationClickedHandler(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        BluxClient.setNotificationClickedHandler { [weak self] notification in
            guard let self = self else { return }
            self.sendEvent(withName: self.NOTIFICATION_CLICKED_EVENT_KEY, body: notification.toDictionary())
        }
        resolve(nil)
    }

    @objc(setInAppUrlOpenOptions:withResolver:withRejecter:)
    func setInAppUrlOpenOptions(options: [String: Any], resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
        let raw = options["httpUrlOpenTarget"] as? String ?? "internalWebView"
        guard let target = Blux.parseHttpUrlOpenTarget(raw) else {
            reject("INVALID_ARGUMENT", "Invalid httpUrlOpenTarget: \(raw)", nil)
            return
        }
        BluxClient.setInAppUrlOpenOptions(InAppUrlOpenOptions(httpUrlOpenTarget: target))
        resolve(nil)
    }

    @objc(startInAppClickedHandler:withRejecter:)
    func startInAppClickedHandler(resolver resolve: @escaping RCTPromiseResolveBlock, rejecter _: RCTPromiseRejectBlock) {
        BluxClient.setInAppClickedHandler { [weak self] inApp in
            guard let self = self else { return }
            self.sendEvent(withName: self.INAPP_CLICKED_EVENT_KEY, body: inApp.toDictionary())
        }
        resolve(nil)
    }

    @objc func startInAppCustomActionHandler() {
        if inAppCustomActionUnsubscribe != nil {
            return
        }

        inAppCustomActionUnsubscribe = BluxClient.addInAppCustomActionHandler { [weak self] actionId, data in
            self?.sendEvent(withName: self?.INAPP_CUSTOM_ACTION_EVENT_KEY ?? "", body: [
                "actionId": actionId,
                "data": data,
            ])
        }
    }

    @objc func dismissInApp() {
        BluxClient.dismissInApp()
    }

    override open func supportedEvents() -> [String] {
        return [NOTIFICATION_CLICKED_EVENT_KEY, NOTIFICATION_FOREGROUND_WILL_DISPLAY_EVENT_KEY, INAPP_CLICKED_EVENT_KEY, INAPP_CUSTOM_ACTION_EVENT_KEY]
    }

    private static func parseHttpUrlOpenTarget(_ raw: String) -> HttpUrlOpenTarget? {
        switch raw {
        case "internalWebView": return .internalWebView
        case "externalBrowser": return .externalBrowser
        case "none": return .none
        default: return nil
        }
    }
}
