import BackendEnvironmentClient
import BrazeKit
import ComposableArchitecture
import Foundation
import InstanceIDClient
import StatsigClient
import UIKit

@DependencyClient
public struct BrazeClient {
    public var configure: @MainActor () throws -> Void
    /// Only call this when logging in. Braze recommends against calling this on logouts
    /// https://www.braze.com/docs/developer_guide/platforms/swift/analytics/setting_user_ids/#assigning-a-user-id
    /// We have logic to handle duplicate calls to this with the same userId, so this should be safe to call multiple times with the same user within the same concurrency context
    public var setUser: (User) -> Void

    public var registerToken: (Data) -> Void
    /// Necessary for logging push analytics
    public var handleBackgroundNotification: @MainActor (_ userInfo: [AnyHashable: Any], _ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Void
    /// Check if notification is internal to Braze
    public var isInternalNotification: (_ userInfo: [AnyHashable: Any]) -> Bool = { _ in false }
    /// Necessary for logging push analytics
    public var handleUserNotification: @MainActor (_ response: UNNotificationResponse, _ completionHandler: @escaping () -> Void) -> Void
    /// Necessary for logging push analytics
    public var handleForegroundNotification: @MainActor (_ notification: UNNotification, _ completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) -> Void
    /// Set idfa and enable ad stracking
    public var setIdfa: (UUID) -> Void
    public var sendEvent: (String) -> Void
}

public extension BrazeClient {
    enum User: Equatable {
        case authenticated(userId: String)
        case anonymous

        public var userId: String? {
            switch self {
            case .authenticated(userId: let id): id
            case .anonymous: nil
            }
        }
    }
}

public enum BrazeClientError: Error {
    case missingAPIKey
    case missingSDKEndpoint

    public var localizedDescription: String {
        switch self {
        case .missingAPIKey:
            return "Configuration is incomplete: missing API key"
        case .missingSDKEndpoint:
            return "Configuration is incomplete: missing service endpoint"
        }
    }
}

extension BrazeClient: DependencyKey {
    private enum Constants {
        static let deviceStableIdAliasLabel = "device_stable_id"
    }

    public static let liveValue: BrazeClient = {
        @Dependency(BackendEnvironmentClient.self) var backendEnvironment
        @Dependency(\.instanceIdClient) var instanceIdClient

        let environmentConfiguration = backendEnvironment.configuration()
        var braze: Braze?
        var brazeUpdatesCancellable: Braze.Cancellable?

        return Self(
            configure: { @MainActor in
                /// Braze initialization must be ran on the main thread in order to enable push automation handling
                /// https://www.braze.com/docs/developer_guide/platforms/swift/push_notifications/#step-3-set-up-push-handling
                let apiKey = environmentConfiguration.brazeApiKey
                guard apiKey.isEmpty == false else {
                    throw BrazeClientError.missingAPIKey
                }

                guard let info = Bundle.main.infoDictionary,
                      let sdkEndpoint = info["BRAZE_SDK_ENDPOINT"] as? String,
                      !sdkEndpoint.isEmpty else {
                    throw BrazeClientError.missingSDKEndpoint
                }

                let configuration = Braze.Configuration(
                    apiKey: apiKey,
                    endpoint: sdkEndpoint
                )
                configuration.forwardUniversalLinks = true

                /// For testflight debugging
                if FeatureFlag.general.brazeDebugLogging {
                    configuration.logger.level = .debug
                }

                braze = Braze(configuration: configuration)
            },
            setUser: { user in
                switch user {
                case .authenticated(userId: let userId):
                    /// Calling `changeUser` in Braze is a costly operation, so let's make sure not to call it if we're already logged in as that user
                    /// https://www.braze.com/docs/developer_guide/platforms/swift/analytics/setting_user_ids/#additional-notes-and-best-practices
                    guard userId != braze?.user.id else { return }
                    braze?.changeUser(userId: userId)

                case .anonymous:
                    /**
                     Don't logout of braze when the user logs out, so we can re-target them
                     https://www.braze.com/docs/developer_guide/platforms/swift/analytics/setting_user_ids#assigning-a-user-id

                     Instead when we sign in with a new user, setting the newUserId will clear the old metadata, remove push notifications for that old user, and register to the new user
                     */

                    // Set device-stable ID as user alias for consistent tracking across login states
                    let deviceStableId = instanceIdClient.instanceId()
                    braze?.user.add(alias: deviceStableId, label: Constants.deviceStableIdAliasLabel)
                }
            },
            registerToken: { tokenData in
                braze?.notifications.register(deviceToken: tokenData)
            },
            handleBackgroundNotification: { @MainActor userInfo, completionHandler in
                guard let braze, braze.notifications.handleBackgroundNotification(userInfo: userInfo, fetchCompletionHandler: completionHandler) else {
                    completionHandler(.noData)
                    return
                }
            },
            isInternalNotification: { userInfo in
                Braze.Notifications.isInternalNotification(userInfo)
            },
            handleUserNotification: { @MainActor response, completionHandler in
                guard let braze, braze.notifications.handleUserNotification(response: response, withCompletionHandler: completionHandler) else {
                    completionHandler()
                    return
                }
            },
            handleForegroundNotification: { @MainActor notification, completionHandler in
                braze?.notifications.handleForegroundNotification(notification: notification)
                completionHandler([.list, .banner])
            },
            setIdfa: { idfa in
                braze?.set(adTrackingEnabled: true)
                braze?.set(identifierForAdvertiser: idfa.uuidString)
            },
            sendEvent: {
                // TODO: do we want properties? will check with Ali
                braze?.logCustomEvent(name: $0, properties: [:])
            }
        )
    }()
}

public extension BrazeClient {
    static let testValue = Self(
        configure: { @MainActor in },
        setUser: { _ in },
        registerToken: { _ in },
        handleBackgroundNotification: { @MainActor _, completionHandler in
            completionHandler(.noData)
        },
        isInternalNotification: { _ in false },
        handleUserNotification: { @MainActor _, completionHandler in
            completionHandler()
        },
        handleForegroundNotification: { @MainActor _, completionHandler in
            completionHandler([.list, .banner])
        },
        setIdfa: { _ in },
        sendEvent: { _ in }
    )

    static let failing = Self(
        configure: { @MainActor in
            throw BrazeClientError.missingAPIKey
        },
        setUser: { _ in },
        registerToken: { _ in },
        handleBackgroundNotification: { @MainActor _, completionHandler in
            completionHandler(.noData)
        },
        isInternalNotification: { _ in false },
        handleUserNotification: { @MainActor _, completionHandler in
            completionHandler()
        },
        handleForegroundNotification: { @MainActor _, completionHandler in
            completionHandler([])
        },
        setIdfa: { _ in },
        sendEvent: { _ in }
    )
}
