import AnalyticsClient
import APIClient
import AppSessionCountClient
import AttributionAndDeeplinkingClient
import BrazeClient
import ClerkClient
import ComposableArchitecture
import DebugFeatureClient
import FirebaseClient
import InAppNotificationClient
import PaywallClient
import StatsigClient
import SunoModelClient
import Utilities

// MARK: - Dependency

public extension DependencyValues {
    var userSessionTransitionClient: UserSessionTransitionClient {
        get { self[UserSessionTransitionClient.self] }
        set { self[UserSessionTransitionClient.self] = newValue }
    }
}

// MARK: - UserSessionTransitionClient

public struct UserSessionTransitionClient {
    /// Main method to handle user session transitions
    public var performUserTransition: @Sendable (
        _ transition: UserTransition
    ) async throws -> UserTransitionResult

    /// Get current service user state (useful for testing)
    public var getServiceUserState: @Sendable () async -> ServiceUserState?
}

// MARK: - Models

public extension UserSessionTransitionClient {
    struct UserTransition {
        let from: Me?
        let to: Me?

        public init(
            from: Me?,
            to: Me?
        ) {
            self.from = from
            self.to = to
        }
    }

    struct UserTransitionResult {
        public let kind: UserTransitionKind
        public let configuredModels: Bool
    }

    enum UserTransitionKind: Equatable {
        case loggedOut
        case loggedIn
        case accountSwitch
        case noOp
    }
}

extension UserSessionTransitionClient.UserTransitionKind {
    init(
        fromUserId: String?,
        toUserId: String?
    ) {
        switch (fromUserId, toUserId) {
        case (.none, .some):
            self = .loggedIn
        case (.some, .none):
            self = .loggedOut
        case (.none, .none):
            self = .noOp
        case (.some(let id1), .some(let id2)):
            if id1 == id2 {
                self = .noOp
            } else {
                self = .accountSwitch
            }
        }
    }
}

// MARK: - DependencyKey

extension UserSessionTransitionClient: DependencyKey {
    public static let liveValue = {
        let actor = UserSessionTransitionActor()

        return Self(
            performUserTransition: { transition in
                try await actor.performUserTransition(transition)
            },
            getServiceUserState: {
                await actor.getServiceUserState()
            }
        )
    }()
}

// MARK: - UserSessionTransitionActor

final actor UserSessionTransitionActor {
    @Dependency(AttributionAndDeeplinkingClient.self) private var attributionClient
    @Dependency(\.analyticsClient) private var analyticsClient
    @Dependency(\.apiClientV2) private var api
    @Dependency(\.appSessionCountClient) private var appSessionCountClient
    @Dependency(BrazeClient.self) private var brazeClient
    @Dependency(\.firebaseClient) private var firebaseClient
    @Dependency(\.inAppNotificationClient) private var inAppNotificationClient
    @Dependency(PaywallClient.self) private var paywallClient
    @Dependency(DebugFeatureClient.self) private var debugFeatureClient
    @Dependency(StatsigClient.self) private var statsigClient
    @Dependency(SunoModelClient.self) private var sunoModelClient
    @Dependency(ClerkClient.self) private var clerkClient

    private var serviceUserState: UserSessionTransitionClient.ServiceUserState?

    init() {}

    func getServiceUserState() -> UserSessionTransitionClient.ServiceUserState? {
        serviceUserState
    }

    func performUserTransition(
        _ transition: UserSessionTransitionClient.UserTransition
    ) async throws -> UserSessionTransitionClient.UserTransitionResult {
        // Create user state from the target user and store it in the actor
        let clerkUser = try? await clerkClient.user()
        let userState = UserSessionTransitionClient.ServiceUserState(
            me: transition.to,
            clerkUser: clerkUser
        )
        serviceUserState = userState

        // Configure all third-party SDKs with the new user state
        await configureThirdPartySDKs(userState: userState)

        let isLoggedOut = transition.to == nil
        if isLoggedOut {
            // User is logged out - clear notifications from persistent store to prevent showing
            // notifications for a user who is no longer authenticated
            inAppNotificationClient.clearNotificationsInPersistentStore()
        }

        /// these flags are used everywhere in the app where you can see v4 vs v3.5 so like on create, the color of the button  on the tab bar and on settings, and others
        /// https://suno-main.slack.com/archives/C06SA9FUV1C/p1740511474965729
        let configuredModels: Bool
        do {
            configuredModels = try await configureSunoModelsIfPossible(me: transition.to)
        } catch {
            configuredModels = false
            log.telemetry.error(error)
        }

        // Track the authentication state transition for analytics
        await trackAuthTransition(transition: transition)

        // Return the transition result with kind and model configuration status
        return UserSessionTransitionClient.UserTransitionResult(
            kind: UserSessionTransitionClient.UserTransitionKind(
                fromUserId: transition.from?.user.id,
                toUserId: transition.to?.user.id
            ),
            configuredModels: configuredModels
        )
    }

    private func configureThirdPartySDKs(
        userState: UserSessionTransitionClient.ServiceUserState
    ) async {
        // Execute synchronous operations immediately
        attributionClient.setUser(userState.attributionUser)

        switch userState.analyticsUser {
        case .authenticated:
            analyticsClient.identify(userState.analyticsUser)
        case .anonymous:
            analyticsClient.resetIdentity()
        }

        brazeClient.setUser(userState.brazeUser)
        firebaseClient.setUser(userState.firebaseUser)

        // Execute async operations in parallel
        async let paywallUpdate: Void = paywallClient.updateUser(userState.paywallUser)

        async let statsigUpdate: Void = {
            do {
                try await statsigClient.setUser(userState.statsigUser)
            } catch {
                log.debug("Statsig failed to set user: \(error)")
            }
        }()

        // Wait for both async operations to complete
        _ = await(paywallUpdate, statsigUpdate)

        // Configure rageshake after statsig has been updated (dependency requirement)
        let isAllowed = debugFeatureClient.isDebugMenuAvailable()
        debugFeatureClient.setRageshakeEnabled(isAllowed)
        debugFeatureClient.setRageshakeUser(userState.rageshakeUser)
    }

    private func configureSunoModelsIfPossible(
        me: Me?
    ) async throws -> Bool {
        guard let me else {
            /// no cleanup necessary, the @DependencyClient handles deallocating this
            return false
        }

        /// these flags are used everywhere in the app where you can see v4 vs v3.5 so like on create, the color of the button  on the tab bar and on settings, and others
        /// https://suno-main.slack.com/archives/C06SA9FUV1C/p1740511474965729

        let sunoModels = me.models
        let userID = me.user.id

        let billingInfo = try await api.getBillingInfo()
        let userAccess = billingInfo.sunoModelUserAccess

        let wrappedModels: [SunoModelMetaData]
        wrappedModels = billingInfo.models.compactMap { SunoModelMetaData($0) }

        let remasterModels = billingInfo.remasterModelTypes?.map { RemasterModelMetaData($0) }

        await sunoModelClient.configure(wrappedModels, userAccess, userID, remasterModels)
        return true
    }

    private func trackAuthTransition(
        transition: UserSessionTransitionClient.UserTransition
    ) async {
        let (from, to) = (transition.from, transition.to)
        let previousAuthState = from.map { SessionAuthenticationState.authenticated(userId: $0.user.id) } ?? .unauthenticated
        let currentAuthState = to.map { SessionAuthenticationState.authenticated(userId: $0.user.id) } ?? .unauthenticated
        await appSessionCountClient.trackAuthenticationTransition(previousAuthState, currentAuthState)
    }
}

// MARK: - ServiceUserState

public extension UserSessionTransitionClient {
    struct ServiceUserState: Equatable {
        public let attributionUser: AttributionAndDeeplinkingClient.User
        public let paywallUser: PaywallClient.User
        public let analyticsUser: AnalyticsClient.User
        public let brazeUser: BrazeClient.User
        public let statsigUser: StatsigClient.User
        public let rageshakeUser: DebugRageshakeUser
        public let firebaseUser: FirebaseClient.User

        init(
            me: Me?,
            clerkUser: ClerkUser?
        ) {
            switch me {
            case .some(let me):
                let user = me.user
                self.attributionUser = .authenticated(userId: user.id)
                self.paywallUser = .authenticated(userId: user.id)
                self.analyticsUser = .authenticated(me: me)
                self.brazeUser = .authenticated(userId: user.id)
                self.statsigUser = .authenticated(
                    userId: user.id,
                    email: user.email,
                    custom: me.statsigCustomProperties?.custom,
                    customIDs: me.statsigCustomProperties?.customIDs
                )
                self.rageshakeUser = .authenticated(
                    id: user.id,
                    email: user.email,
                    displayName: user.displayName,
                    handle: user.handle
                )
                self.firebaseUser = .authenticated(userId: user.id, email: user.email)

            case .none:
                self.attributionUser = .anonymous
                self.paywallUser = .anonymous
                self.analyticsUser = .anonymous
                self.brazeUser = .anonymous
                self.statsigUser = .anonymous
                self.rageshakeUser = .anonymous
                self.firebaseUser = .anonymous
            }
        }

        public var allAuthenticated: Bool {
            if case .authenticated = attributionUser,
               case .authenticated = paywallUser,
               case .authenticated = analyticsUser,
               case .authenticated = brazeUser,
               case .authenticated = statsigUser,
               case .authenticated = rageshakeUser,
               case .authenticated = firebaseUser
            {
                return true
            }
            return false
        }

        public var allAnonymous: Bool {
            if case .anonymous = attributionUser,
               case .anonymous = paywallUser,
               case .anonymous = analyticsUser,
               case .anonymous = brazeUser,
               case .anonymous = statsigUser,
               case .anonymous = rageshakeUser,
               case .anonymous = firebaseUser
            {
                return true
            }
            return false
        }

        public func allHave(
            id: String
        ) -> Bool {
            let userIds = [
                attributionUser.userId,
                paywallUser.userId,
                analyticsUser.me?.user.id,
                brazeUser.userId,
                statsigUser.userId,
                rageshakeUser.userId,
                firebaseUser.userId,
            ]

            if userIds.isEmpty {
                return false
            }

            return userIds.allSatisfy { $0 == id }
        }
    }
}
