import ComposableArchitecture
import FeatureMadlibsOnboarding
import FeatureOnboardingModels
import FeatureSocial
import Localization
import OnboardingAnalyticsClient
import StatsigClient
import APIClient

extension OnboardingReducer {
    func handleInternalAction(
        _ internalAction: Action.Internal,
        state: inout State
    ) -> Effect<Action> {
        switch internalAction {
        case .initializeExperimentFlags:
            let variant: OnboardingFlowVariant

            // Use feature flag to gate the experiment
            let isMadlibsOnboardingExperimentEnabled = FeatureFlag.legacy.madlibsOnboardingExperimentEnabled
            if isMadlibsOnboardingExperimentEnabled {
                // Very important note - we don't send an exposure event here
                // Exposure event for the madlibs flow is sent later just before they see the treatment,
                // if this same feature flag is enabled

                // Here, we derive the OnboardingFlowVariant from the experiment's parameter value
                // madlibsOnboardingExperiment == true -> show madlibs flow
                // madlibsOnboardingExperiment == false -> show default flow
                variant = if FeatureFlag.legacy.madlibsOnboardingExperiment {
                    OnboardingFlowVariant.madlibs
                } else {
                    OnboardingFlowVariant.default
                }
            } else {
                variant = .default
            }

            let experimentFlags = ExperimentFlags(
                madlibs: .init(
                    isEnabled: isMadlibsOnboardingExperimentEnabled
                ),
                onboardingFlow: variant
            )

            state.experimentFlags = experimentFlags

            return .send(.internal(.experimentFlagsLoaded(experimentFlags)))

        case .experimentFlagsLoaded(let flags):
            state.flowVariant = state.overrideFlowVariant ?? flags.onboardingFlow
            state.hasExperimentFlags = true
            return .send(.internal(.startOnboarding))

        case .startOnboarding:
            guard !state.isOnboardingStarted else { return .none }
            state.isOnboardingStarted = true
            return .none

        case .getMe(isFirstTimeUser: let isFirstTimeUser):
            state.isFTUX = isFirstTimeUser

            // Track that we're starting to load the current user
            onboardingAnalyticsClient.trackLoadCurrentUserStarted()

            let flowVariant = state.flowVariant

            return .run { send in
                let me: Me
                let data: GetMeResponseData

                do {
                    me = try await apiClient.getMe()

                    // Track sign up success if this is a first time user
                    // We want to track this as soon as possible after we have successfully authenticated & fetched me
                    // This event is especially important for Singular attribution - take great care
                    if isFirstTimeUser {
                        onboardingAnalyticsClient.trackSignUpSucceeded(flowVariant, me.user.id)
                    }

                    data = GetMeResponseData(
                        me: me,
                        isFirstTimeUser: isFirstTimeUser
                    )
                } catch {
                    await send(.internal(.getMeResponse(.failure(.getMeFailed))))
                    return
                }

                do {
                    try await statsigClient.setUser(.authenticated(
                        userId: me.user.id,
                        email: me.user.email,
                        custom: me.statsigCustomProperties?.custom,
                        customIDs: me.statsigCustomProperties?.customIDs
                    ))
                } catch {
                    // Don't block sign in on Statsig setUser failures,
                    // either show cached values (OK) or default values
                    log.telemetry.error(error, message: "Statsig setUser failed, isFirstTimeUser: \(isFirstTimeUser)")
                }

                // Wait until StatsigClient calls setUser before returning this response
                // in case previous flows had timing assumptions here (TODO: BA)
                await send(.internal(.getMeResponse(.success(data))))
            }

        case .getMeResponse(let result):
            switch result {
            case .success(let data):
                let (me, isFirstTimeUser) = (data.me, data.isFirstTimeUser)

                // If we are skipping auth, we want to show the onboarding flow
                let notSkippingAuth = !state.skipAuth
                let shouldSkipOnboarding = !isFirstTimeUser && notSkippingAuth
                if shouldSkipOnboarding {
                    // Don't track any onboarding events for returning users who skip onboarding
                    return .send(.delegate(.onboardingSkippedForReturningUser(me)))
                }

                // Track flow started only for users who will actually see onboarding
                // This happens after auth when we've determined they need onboarding
                onboardingAnalyticsClient.trackFlowStarted(state.flowVariant, me.user.id)

                state.currentMe = me
                if let fullName = state.fullNameFromAuth, !fullName.isEmpty {
                    state.currentMe?.user.displayName = fullName
                }

                let navigationResult = OnboardingNavigation.navigateAfterAuthentication(state: &state)
                return navigationResult.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)

            case .failure(let error):
                state.alert = AlertState(
                    title: { TextState(L10n.FeatureOnboarding.errorGeneric) },
                    actions: {
                        ButtonState(action: .dismiss) {
                            TextState(L10n.FeatureOnboarding.errorButton)
                        }
                    },
                    message: { TextState(error.localizedDescription) }
                )
                return .none
            }
        }
    }

    func handleSignUpDelegate(
        _ delegateAction: SignUp.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let stepType = OnboardingStepType.signUp

        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(
                stepType,
                state.flowVariant,
                state.currentMe?.user.id
            )
            return .none

        case .flowCompleted(let flowResult):
            switch flowResult {
            case .phoneSignInSelected:
                onboardingAnalyticsClient.trackAuthMethodSelected(.phone, state.flowVariant)
                state.path.append(.phoneSignIn(PhoneSignIn.State()))
                return .none

            case .authenticated(let isFirstTimeUser):
                // Track authentication success
                // Note - we don't have the user id yet, so we track successful sign up on successful response of getMe
                onboardingAnalyticsClient.trackAuthSucceeded(state.flowVariant, isFirstTimeUser)
                return .send(.internal(.getMe(isFirstTimeUser: isFirstTimeUser)))

            case .authenticatedWithFullName(let isFirstTimeUser, let fullName):
                state.fullNameFromAuth = fullName
                // Track authentication success
                // Note - we don't have the user id yet, so we track successful sign up on successful response of getMe
                onboardingAnalyticsClient.trackAuthSucceeded(state.flowVariant, isFirstTimeUser)
                return .send(.internal(.getMe(isFirstTimeUser: isFirstTimeUser)))

            case .cancelled:
                return handleBackNavigation(state: &state)
            }

        case .authenticationError:
            // Track authentication failure
            // Note: We don't have the specific error details here so we pass nil
            onboardingAnalyticsClient.trackAuthFailed(nil, state.flowVariant)
            return .none

        case .authMethodSelected(let method):
            // Track auth method selection
            onboardingAnalyticsClient.trackAuthMethodSelected(
                method.asOnboardingMethod,
                state.flowVariant
            )
            return .none
        }
    }

    func handlePhoneSignInDelegate(
        _ delegateAction: PhoneSignIn.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(.phoneAuth, state.flowVariant, state.currentMe?.user.id)
            return .none

        case .phoneCodeSent(let phoneNumber):
            // Track that phone code was sent
            onboardingAnalyticsClient.trackPhoneCodeSent(phoneNumber, state.flowVariant)

            let phoneCodeEntryState = PhoneCodeEntry.State(
                phone: phoneNumber,
                path: .signIn
            )
            state.path.append(.phoneCode(phoneCodeEntryState))
            return .none

        case .cancelTapped:
            return handleBackNavigation(state: &state)
        }
    }

    func handlePhoneCodeDelegate(
        _ delegateAction: PhoneCodeEntry.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(.codeVerification, state.flowVariant, state.currentMe?.user.id)
            return .none

        case .cancelTapped:
            return handleBackNavigation(state: &state)

        case .validationSucceeded(let path, let authInfo):
            switch path {
            case .signIn:
                let isFirstTimeUser = authInfo?.isFirstTimeUser ?? false
                state.fullNameFromAuth = authInfo?.fullName ?? nil
                return .send(.internal(.getMe(isFirstTimeUser: isFirstTimeUser)))

            default:
                return .none
            }
        }
    }

    func handleDisplayNameDelegate(
        _ delegateAction: NameEntry.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let stepType = OnboardingStepType.displayNameSelection

        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(stepType, state.flowVariant, state.currentMe?.user.id)
            return .none

        case .updateNameComplete(let me):
            state.currentMe = me
            state.completedSteps.insert(stepType)

            let successfulUpdate = true
            let displayName = me.user.displayName
            onboardingAnalyticsClient.trackDisplayNameUpdated(
                successfulUpdate,
                displayName,
                nil,
                state.flowVariant
            )

            let result = OnboardingNavigation.navigateToNextStep(after: stepType, state: &state, me: me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)

        case .updateNameFailed(let error):
            let successfulUpdate = false
            let displayName: String? = nil
            onboardingAnalyticsClient.trackDisplayNameUpdated(
                successfulUpdate,
                displayName,
                error.localizedDescription,
                state.flowVariant
            )
            return .none

        case .backTapped:
            onboardingAnalyticsClient.trackBackTapped(stepType, state.flowVariant)
            return handleBackNavigation(state: &state)

        case .displayNameFieldFocused:
            onboardingAnalyticsClient.trackUpdateDisplayNameTapped(
                state.currentMe?.user.displayName ?? "",
                state.flowVariant
            )
            return .none
        }
    }

    func handleUsernameDelegate(
        _ delegateAction: UsernameEntry.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let stepType = OnboardingStepType.usernameSelection

        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(stepType, state.flowVariant, state.currentMe?.user.id)
            return .none

        case .updateUsernameComplete(let me):
            state.currentMe = me
            state.completedSteps.insert(stepType)
            onboardingAnalyticsClient.trackUsernameUpdated(true, me.user.handle, nil, state.flowVariant)
            let result = OnboardingNavigation.navigateToNextStep(after: stepType, state: &state, me: me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)

        case .backTapped:
            onboardingAnalyticsClient.trackBackTapped(stepType, state.flowVariant)
            return handleBackNavigation(state: &state)

        case .usernameFieldFocused:
            onboardingAnalyticsClient.trackUpdateHandleTapped(state.flowVariant)
            return .none

        case .updateUsernameFailed(let error):
            onboardingAnalyticsClient.trackUsernameUpdated(false, nil, error.localizedDescription, state.flowVariant)
            return .none
        }
    }

    func handleBirthdayDelegate(
        _ delegateAction: BirthdayEntry.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let stepType = OnboardingStepType.birthdayEntry

        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(
                stepType,
                state.flowVariant,
                state.currentMe?.user.id
            )
            return .none

        case .updateBirthdayComplete(let me):
            state.currentMe = me
            state.completedSteps.insert(.birthdayEntry)
            // Track birthday updated
            onboardingAnalyticsClient.trackBirthdayUpdated(state.flowVariant)
            let result = OnboardingNavigation.navigateToNextStep(after: stepType, state: &state, me: me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)

        case .backTapped:
            onboardingAnalyticsClient.trackBackTapped(
                stepType,
                state.flowVariant
            )
            return handleBackNavigation(state: &state)
        }
    }

    func handleNotificationsDelegate(
        _ delegateAction: OnboardingNotificationsQuestion.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let stepType = OnboardingStepType.notificationPermissions

        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(stepType, state.flowVariant, state.currentMe?.user.id)
            return .none

        case .showNotification(let me):
            state.currentMe = me
            state.completedSteps.insert(stepType)
            // Track notification permission requested
            onboardingAnalyticsClient.trackNotificationPermissionRequested(state.flowVariant, stepType)
            let result = OnboardingNavigation.navigateToNextStep(after: stepType, state: &state, me: me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)

        case .tappedSkip(let me):
            state.currentMe = me
            state.completedSteps.insert(stepType)
            // Track notification permission skipped
            onboardingAnalyticsClient.trackSkipTapped(stepType, state.flowVariant)
            let result = OnboardingNavigation.navigateToNextStep(after: .notificationPermissions, state: &state, me: me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)

        case .tappedBack:
            onboardingAnalyticsClient.trackBackTapped(stepType, state.flowVariant)
            return handleBackNavigation(state: &state)
        }
    }

    func handleContactsAuthDelegate(
        _ delegateAction: ContactsAuthorization.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let stepType = OnboardingStepType.contactPermissions

        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(stepType, state.flowVariant, state.currentMe?.user.id)
            return .none

        case .authorized(let me):
            state.currentMe = me
            state.completedSteps.insert(.contactPermissions)

            // Set contactSyncSeen when navigating to contact sync
            state.$contactSyncSeen.withLock { $0 = true }

            let result = OnboardingNavigation.navigateToNextStep(after: stepType, state: &state, me: me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)

        case .denied(let me):
            state.currentMe = me
            state.completedSteps.insert(stepType)

            // Track onboarding flow completed
            let result = OnboardingNavigation.NavigationResult.completeOnboarding(me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)
        }
    }

    func handleContactSyncDelegate(
        _ delegateAction: ContactSync.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        switch delegateAction {
        case .onAppear:
            onboardingAnalyticsClient.trackScreenView(.contactSync, state.flowVariant, state.currentMe?.user.id)
            return .none

        case .doneButtonTapped(let me):
            state.currentMe = me
            state.completedSteps.insert(.contactPermissions)
            if case .contactSync = state.path.last {
                state.completedSteps.insert(.contactSync)
            }

            // Track done button tap
            onboardingAnalyticsClient.trackDoneTapped(.contactSync, state.flowVariant)

            let navigationResult = OnboardingNavigation.NavigationResult.completeOnboarding(me)
            return navigationResult.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)
        }
    }

    func handleMadlibsContainerDelegate(
        _ delegateAction: MadlibsContainerReducer.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        switch delegateAction {
        case .profileSetupCompleted(let me):
            state.currentMe = me
            let result = OnboardingNavigation.navigateToNextStep(after: .madlibsFlow, state: &state, me: me)
            return result.toEffect(state: state, onboardingAnalyticsClient: onboardingAnalyticsClient)
        }
    }

    func handleBackNavigation(state: inout State) -> Effect<Action> {
        // Check if we have screens pushed on the navigation stack
        let hasScreensInPath = state.path.count > 0

        if hasScreensInPath {
            // Pop the last screen from the navigation stack
            state.path.removeLast()
            return .none
        } else {
            // We're at the root SignUpScreen, notify parent coordinator
            // that user wants to go back from the onboarding flow
            return .send(.delegate(.backTappedOnRoot))
        }
    }
}

// MARK: - Private Extensions

private typealias OnboardingSignInMethod = FeatureOnboardingModels.SignInMethod

private extension OnboardingFlowVariant {
    static func fromExperimentValue(_ experimentValue: String) -> OnboardingFlowVariant {
        switch experimentValue {
        case "madlibs_flow":
            return .madlibs

        default:
            return .default
        }
    }
}

private extension SignUp.SignInMethod {
    var asOnboardingMethod: OnboardingSignInMethod {
        switch self {
        case .phone: .phone
        case .apple: .apple
        case .google: .google
        case .microsoft: .microsoft
        case .discord: .discord
        }
    }
}
