//
//  HackleReactNativeExtensions.swift
//  HackleReactNativeSdk
//
//  Created by hackle on 10/10/24.
//  Copyright © 2024 Facebook. All rights reserved.
//

import Foundation
import Hackle
import React

extension HackleReactNativeSdk: HackleInAppMessageDelegate {
    public func inAppMessageWillAppear(inAppMessage: any HackleInAppMessage) {
        if !self.hasEventListener {
            return
        }

        sendEvent(withName: HackleInAppMessageEvents.beforeOpen.rawValue, body: [
            "inAppMessage": inAppMessage.toMap()
        ])
    }

    public func inAppMessageDidAppear(inAppMessage: any HackleInAppMessage) {
        if !self.hasEventListener {
            return
        }

        sendEvent(withName: HackleInAppMessageEvents.afterOpen.rawValue, body: [
            "inAppMessage": inAppMessage.toMap()
        ])
    }

    public func inAppMessageWillDisappear(inAppMessage: any HackleInAppMessage) {
        if !self.hasEventListener {
            return
        }

        sendEvent(withName: HackleInAppMessageEvents.beforeClose.rawValue, body: [
            "inAppMessage": inAppMessage.toMap()
        ])
    }
    public func inAppMessageDidDisappear(inAppMessage: any HackleInAppMessage) {
        if !self.hasEventListener {
            return
        }

        sendEvent(withName: HackleInAppMessageEvents.afterClose.rawValue, body: [
            "inAppMessage": inAppMessage.toMap()
        ])
    }

    public func onInAppMessageClick(inAppMessage: any HackleInAppMessage, view: any HackleInAppMessageView, action: any HackleInAppMessageAction) -> Bool {
        if !self.hasEventListener {
            return false
        }

        guard inAppMessageClickLock.try() else {
            os_log("%@", log: .hackle, type: .info, "InAppMessage click event blocked - concurrent access detecte.")
            return true
        }

        defer {
            inAppMessageClickLock.unlock()
        }

        self.inAppMessageView = view

        sendEvent(withName: HackleInAppMessageEvents.onClick.rawValue, body: [
            "inAppMessage": inAppMessage.toMap(),
            "action": action.toMap()
        ])

        var returnValue: Bool = false
        let semaphore = DispatchSemaphore(value: 0)

        self.inAppMessageOnClickResolveBlock = { result in
            returnValue = result
            semaphore.signal()
        }

        _ = semaphore.wait(timeout: .now() + .milliseconds(100))
        return returnValue
    }
}

extension NSDictionary {
    func toHackleConfig() -> HackleConfig {
        let hackleConfigBuilder = HackleConfigBuilder()

        if let wrapperName = object(forKey: "wrapperName") as? String {
            _ = hackleConfigBuilder.add("$wrapper_name", wrapperName)
        }

        if let wrapperVersion = object(forKey: "wrapperVersion") as? String {
            _ = hackleConfigBuilder.add("$wrapper_version", wrapperVersion)
        }

        if let debug = object(forKey: "debug") as? Bool, debug {
            _ = hackleConfigBuilder.eventFlushIntervalSeconds(1)
        }

        if let sdkUrl = object(forKey: "sdkUrl") as? String,
           let url = URL(string: sdkUrl) {
            _ = hackleConfigBuilder.sdkUrl(url)
        }

        if let eventUrl = object(forKey: "eventUrl") as? String,
           let url = URL(string: eventUrl) {
            _ = hackleConfigBuilder.eventUrl(url)
        }

        if let monitoringUrl = object(forKey: "monitoringUrl") as? String,
           let url = URL(string: monitoringUrl) {
            _ = hackleConfigBuilder.monitoringUrl(url)
        }

        if let sessionTimeoutMillis = object(forKey: "sessionTimeoutMillis") as? NSNumber {
            _ = hackleConfigBuilder.sessionTimeoutIntervalSeconds(sessionTimeoutMillis.doubleValue / 1000)
        }

        if let pollingIntervalMillis = object(forKey: "pollingIntervalMillis") as? NSNumber {
            _ = hackleConfigBuilder.pollingIntervalSeconds(pollingIntervalMillis.doubleValue / 1000)
        }

        if let eventFlushIntervalMillis = object(forKey: "eventFlushIntervalMillis") as? NSNumber {
            _ = hackleConfigBuilder.eventFlushIntervalSeconds(eventFlushIntervalMillis.doubleValue / 1000)
        }

        if let eventFlushThreshold = object(forKey: "eventFlushThreshold") as? NSNumber {
            _ = hackleConfigBuilder.eventFlushThreshold(eventFlushThreshold.intValue)
        }

        if let exposureEventDedupIntervalMillis = object(forKey: "exposureEventDedupIntervalMillis") as? NSNumber {
            _ = hackleConfigBuilder.exposureEventDedupIntervalSeconds(exposureEventDedupIntervalMillis.doubleValue / 1000)
        }

        if let automaticAppLifecycleTracking = object(forKey: "automaticAppLifecycleTracking") as? Bool {
            _ = hackleConfigBuilder.automaticAppLifecycleTracking(automaticAppLifecycleTracking)
        }

        if let automaticScreenTracking = object(forKey: "automaticScreenTracking") as? Bool {
            _ = hackleConfigBuilder.automaticScreenTracking(automaticScreenTracking)
        }

        if let enableMonitoring = object(forKey: "enableMonitoring") as? Bool {
            _ = hackleConfigBuilder.monitoringEnabled(enableMonitoring)
        }

        if let optOutTracking = object(forKey: "optOutTracking") as? Bool {
            _ = hackleConfigBuilder.optOutTracking(optOutTracking)
        }

        if let evaluationMode = object(forKey: "evaluationMode") as? String {
            switch evaluationMode {
            case "local":
                _ = hackleConfigBuilder.evaluationMode(.local)
            case "remote":
                _ = hackleConfigBuilder.evaluationMode(.remote)
            default:
                os_log("%@", log: .hackle, type: .info, "Unknown evaluationMode: \(evaluationMode)")
            }
        }

        if let sessionPolicyDict = object(forKey: "sessionPolicy") as? [String: Any] {
            let sessionPolicyBuilder = HackleSessionPolicy.builder()

            if let persistCondition = sessionPolicyDict["persistCondition"] as? String {
                switch persistCondition {
                case "alwaysNewSession":
                    sessionPolicyBuilder.persistCondition(.alwaysNewSession)
                case "nullToUserId":
                    sessionPolicyBuilder.persistCondition(.nullToUserId)
                default:
                    os_log("%@", log: .hackle, type: .info, "Unknown persistCondition: \(persistCondition)")
                }
            }

            if let timeoutDict = sessionPolicyDict["timeoutCondition"] as? [String: Any] {
                let timeoutBuilder = HackleSessionTimeoutCondition.builder()
                if let timeoutMillis = timeoutDict["timeoutIntervalMillis"] as? NSNumber {
                    timeoutBuilder.timeoutIntervalSeconds(timeoutMillis.doubleValue / 1000)
                }
                if let onForeground = timeoutDict["onForeground"] as? Bool {
                    timeoutBuilder.onForeground(onForeground)
                }
                if let onBackground = timeoutDict["onBackground"] as? Bool {
                    timeoutBuilder.onBackground(onBackground)
                }
                if let onApplicationStateChange = timeoutDict["onApplicationStateChange"] as? Bool {
                    timeoutBuilder.onApplicationStateChange(onApplicationStateChange)
                }
                sessionPolicyBuilder.timeoutCondition(timeoutBuilder.build())
            }

            _ = hackleConfigBuilder.sessionPolicy(sessionPolicyBuilder.build())
        }

        return hackleConfigBuilder.build()
    }

    func toUser() -> User {
        let builder = User.builder()

        if let id = object(forKey: "id") as? String { builder.id(id) }
        if let userId = object(forKey: "userId") as? String { builder.userId(userId) }
        if let deviceId = object(forKey: "deviceId") as? String { builder.deviceId(deviceId) }
        if let identifiers = object(forKey: "identifiers") as? [String: String] { builder.identifiers(identifiers) }
        if let properties = object(forKey: "properties") as? [String: Any?] { builder.properties(properties) }

        return builder.build()
    }

    func toEvent() throws -> Event {
        guard let eventKey: String = object(forKey: "key") as? String, !eventKey.isEmpty else {
            throw HackleError.error("event key must be required")
        }
        let value = object(forKey: "value") as? Double ?? 0.0
        let properties: [String: Any]? = object(forKey: "properties") as? [String: Any]
        return Hackle.event(key: eventKey, value: value, properties: properties)
    }

    func toPropertyOperations() -> PropertyOperations {
        let converted: [String: [String: Any?]] = self as? [String: [String: Any?]] ?? [:]
        let builder = PropertyOperationsBuilder()

        for (operation, values) in converted {
            switch operation {
                case "$set":
                    for (key, value) in values {
                        builder.set(key, value)
                    }
                case "$setOnce":
                    for (key, value) in values {
                        builder.setOnce(key, value)
                    }
                case "$unset":
                    for (key, _) in values {
                        builder.unset(key)
                    }
                case "$increment":
                    for (key, value) in values {
                        builder.increment(key, value)
                    }
                case "$append":
                    for (key, value) in values {
                        builder.append(key, value)
                    }
                case "$appendOnce":
                    for (key, value) in values {
                        builder.appendOnce(key, value)
                    }
                case "$prepend":
                    for (key, value) in values {
                        builder.prepend(key, value)
                    }
                case "$prependOnce":
                    for (key, value) in values {
                        builder.prependOnce(key, value)
                    }
                case "$remove":
                    for (key, value) in values {
                        builder.remove(key, value)
                    }
                case "$clearAll":
                    builder.clearAll()
                default:
                    break
            }
        }

        return builder.build()
    }

    func toHackleSubscriptionOperations() -> HackleSubscriptionOperations {
        let converted: [String: String] = self as? [String: String] ?? [:]
        let builder = HackleSubscriptionOperationsBuilder()
        for (key, value) in converted {
            if let status = HackleSubscriptionStatus(rawValue: value) {
                builder.custom(key, status: status)
            }
        }
        return builder.build()
    }

    func toScreen() -> Screen {
        let name = object(forKey: "name") as? String ?? ""
        let screenClass = object(forKey: "screenClass") as? String ?? ""
        let properties = object(forKey: "properties") as? [String: Any]

        let screenBuilder = Screen.builder(name: name, className: screenClass)

        if let properties = properties {
            screenBuilder.properties(properties)
        }

        return screenBuilder.build()
    }
}

extension User {
    func toMap() -> [String: Any] {
        return [
            "id": id,
            "userId": userId,
            "deviceId": deviceId,
            "properties": properties,
            "identifiers": identifiers
        ]
    }
}

extension Decision {
    func toMap() -> [String: Any] {
        return [
            "variation": variation,
            "reason": reason,
            "parameters": parameters
        ]
    }
}

extension FeatureFlagDecision {
    func toMap() -> [String: Any] {
        return [
            "isOn": isOn,
            "reason": reason,
            "parameters": parameters
        ]
    }
}

extension HackleInAppMessage {
    func toMap() -> [String: Any] {
        return [
            "key": key
        ]
    }
}

extension HackleInAppMessageAction {
    func toMap() -> [String: Any?] {
        if type == .close {
            return [
                "type": type.rawValue,
                "close": [
                    "hideDurationMillis": close?.hideDuration ?? 0
                ]
            ]
        } else if type == .link {
            return [
                "type": type.rawValue,
                "link": [
                    "url": link?.url ?? "",
                    "shouldCloseAfterLink": link?.shouldCloseAfterLink ?? false
                ]
            ]
        } else {
            return ["type": type.rawValue]
        }
    }
}

extension OSLog {
    static let hackle = OSLog(subsystem: "io.hackle.sdk", category: "Hackle")
}
