import AnalyticsClient
import APIClient
import BackendEnvironmentClient
import ComposableArchitecture
import DeeplinkIntents
import Foundation
import InstanceIDClient
import Singular
import UIKit.UIApplication
import Utilities

private enum DeeplinkingError: Error {
    case deeplinkValueNotSupported(deeplinkValue: String, conversionData: [AnyHashable: Any])
}

@DependencyClient
public struct AttributionAndDeeplinkingClient {
    /// Setup
    public var configure: () -> Void
    public var setUser: (User) -> Void

    /// UDL (Unified Deep Linking)
    public var continueUserActivity: (_ userActivity: NSUserActivity) -> Void
    public var handleOpenUrl: (URL, _ options: [UIApplication.OpenURLOptionsKey: Any]) -> Void
    public var delegate: @Sendable () -> AsyncStream<DelegateEvent> = { .never }

    /// In app Events
    public var track: (AttributionPartnerEvent) -> Void

    public enum AttributionPartnerEvent {
        case signUp(sunoUserId: String)
    }

    public enum DelegateEvent {
        case didResolveDeeplink(DeeplinkIntent)
    }
}

public extension AttributionAndDeeplinkingClient {
    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 AttributionPartnerDeeplinkIntent: Sendable {
    case shareDeeplink(shareContentId: String, shareAttributionId: String?, shareContentType: String?)

    init?(deeplinkPartnerClickEvent: [AnyHashable: Any]) {
        guard let scid = deeplinkPartnerClickEvent["scid"] as? String else {
            return nil
        }

        self = .shareDeeplink(
            shareContentId: scid,
            shareAttributionId: deeplinkPartnerClickEvent["sh"] as? String,
            shareContentType: deeplinkPartnerClickEvent["sct"] as? String
        )
    }

    public var asDeeplink: DeeplinkIntent {
        switch self {
        case let .shareDeeplink(shareContentId, shareAttributionId, _):
            DeeplinkIntent.song(songId: shareContentId, sh: shareAttributionId)
        }
    }
}

extension AttributionAndDeeplinkingClient: DependencyKey {
    public static var liveValue: Self {
        guard let info = Bundle.main.infoDictionary,
              let singularAPIKey = info["SINGULAR_API_KEY"] as? String,
              !singularAPIKey.isEmpty else {
            assertionFailure("Missing Singular API key in Info.plist")
            return .noop
        }

        guard let singularAPISecret = info["SINGULAR_API_SECRET"] as? String,
              !singularAPISecret.isEmpty else {
            assertionFailure("Missing Singular API secret in Info.plist")
            return .noop
        }

        @Dependency(\.instanceIdClient) var instanceIdClient

        let config = SingularConfig(apiKey: singularAPIKey, andSecret: singularAPISecret)

        /// Don't wait to attribute, attribute immediately. we've decided it's ok to not have an IDFA in the install event on Singular
        config?.waitForTrackingAuthorizationWithTimeoutInterval = 0
        config?.skAdNetworkEnabled = true
        config?.enableOdmWithTimeoutInterval = 5

        let delegateStream = AsyncStream<DelegateEvent> { [weak config] continuation in
            config?.singularLinksHandler = { (params: SingularLinkParams?) in
                if let params {
                    let deeplink = params.getDeepLink()
                    let isDeferredDeeplink = params.isDeferred()
                    log.info("got deeplink from Singular: \(deeplink ?? ""). deferred=\(isDeferredDeeplink)")
                    if let deeplink, let deeplinkUrl = URL(string: deeplink), let deeplinkIntent = DeeplinkIntent(url: deeplinkUrl) {
                        continuation.yield(.didResolveDeeplink(deeplinkIntent))
                    } else {
                        log.telemetry.assertionFailure("deeplink from singular didn't seem to have an underlying url")
                    }
                }
            }
        }

        let instance = Self(
            configure: {
                Singular.start(config)
            },
            setUser: { user in
                // When a user is authenticated, we want to give Singular the user's id
                // When a user is logged out, we want to give Singular the user's device id (the instance id)
                let id = switch user {
                case .authenticated(userId: let id): id
                case .anonymous: instanceIdClient.instanceId()
                }

                Singular.setCustomUserId(id)
            },
            continueUserActivity: { userActivity in
                config?.userActivity = userActivity
                Singular.start(config)
            },
            handleOpenUrl: { url, _ in
                config?.openUrl = url
                Singular.start(config)
            },
            delegate: { delegateStream },
            track: { event in
                switch event {
                case .signUp(let sunoUserId):
                    Singular.event("sng_complete_registration", withArgs: ["suno_user_id": sunoUserId])
                }
            }
        )

        return instance
    }

    public static var testValue: Self = noop

    public static var noop: Self {
        Self(
            configure: {},
            setUser: { _ in },
            continueUserActivity: { _ in },
            handleOpenUrl: { _, _ in },
            delegate: { .never },
            track: { _ in }
        )
    }
}
