import APIClient
import ComposableArchitecture
import FeatureMadlibsOnboardingModels
import FeatureOnboardingModels
import OnboardingAnalyticsClient
import UIKit

// MARK: - Action Handlers

extension MadlibsContainerReducer {
    func handleUserAction(
        _ action: UserAction,
        state: inout State
    ) -> Effect<Action> {
        switch action {
        case .nextTapped:
            let madlibsStep = state.currentStep.toMadlibsStep
            onboardingAnalyticsClient.trackMadlibsEvent(.nextTapped(currentStep: madlibsStep))

            guard state.canNavigateNext else {
                return .none
            }

            switch state.currentStep {
            case .musicGenre:
                return .send(.musicGenre(.internal(.saveSelection)))

            case .displayName:
                return .send(.displayName(.updateNameTapped))

            case .username:
                return .send(.username(.updateUsernameTapped))

            case .birthday:
                return .send(.birthday(.updateBirthdayTapped))

            case .notifications:
                return .send(.notifications(.tappedShowNotification))
            }

        case .backTapped:
            let madlibsStep = state.currentStep.toMadlibsStep
            let previousMadlibsStep = state.currentStep.previous?.toMadlibsStep
            onboardingAnalyticsClient.trackMadlibsEvent(.backTapped(currentStep: madlibsStep, previousStep: previousMadlibsStep))

            guard state.canNavigateBack else {
                return .none
            }
            return .send(.navigation(.goToPrevious))

        case .skipTapped:
            let madlibsStep = state.currentStep.toMadlibsStep
            let nextMadlibsStep = state.currentStep.next?.toMadlibsStep
            onboardingAnalyticsClient.trackMadlibsEvent(.skipTapped(currentStep: madlibsStep, nextStep: nextMadlibsStep))

            switch state.currentStep {
            case .musicGenre:
                state.completedSteps.insert(.musicGenre)

                // Attempt to save selection with whatever is selected - failing silently is okay here
                return .send(.musicGenre(.internal(.saveSelection)))
                    .concatenate(with: navigateToNextOrComplete(from: state.currentStep, me: state.me))

            case .username:
                state.completedSteps.insert(.username)

                // Attempt to update username with whatever is in the textfield - failing silently is okay here
                return .send(.username(.updateUsernameTapped))
                    .concatenate(with: navigateToNextOrComplete(from: state.currentStep, me: state.me))

            case .displayName, .birthday, .notifications:
                state.completedSteps.insert(state.currentStep)
                return navigateToNextOrComplete(from: state.currentStep, me: state.me)
            }
        }
    }

    func handleNavigationAction(
        _ navigationAction: Action.Navigation,
        state: inout State
    ) -> Effect<Action> {
        switch navigationAction {
        case .moveToStep(let step):
            resetFocusStatesConditionally(state: &state, to: step)

            let navigationDirection = State.NavigationDirection.forward

            switch step {
            case .notifications:
                state.notificationsState = OnboardingNotificationsQuestion.State(me: state.me)
                state.previousStep = state.currentStep
                state.navigationDirection = navigationDirection
                state.currentStep = step

                // Subscribe to notification status updates
                return .merge(
                    .run { send in
                        let status = await userNotificationsClient.getNotificationStatus()
                        await send(.internal(.notificationStatusUpdate(status)))
                    },
                    .stream(
                        userNotificationsClient.notificationStatusChannel(),
                        send: { .internal(.notificationStatusUpdate($0)) },
                        cancellableId: NotificationStatusCancellable()
                    )
                )

            case .musicGenre:
                state.previousStep = state.currentStep
                state.navigationDirection = navigationDirection
                state.currentStep = step
                return .none

            case .displayName:
                state.displayNameState = NameEntry.State(me: state.me, errorRepresentation: .userFacingText)
                let previousStep = state.currentStep
                state.previousStep = previousStep
                state.navigationDirection = navigationDirection
                state.currentStep = step

                // Maintain keyboard focus when coming from username
                let textInputSteps = State.ProfileSetupStep.textInputSteps
                if textInputSteps.contains(previousStep) {
                    state.displayNameState?.isFocused = true
                }
                return .none

            case .username:
                state.usernameState = UsernameEntry.State(me: state.me)
                let previousStep = state.currentStep
                state.previousStep = previousStep
                state.navigationDirection = navigationDirection
                state.currentStep = step

                // Maintain keyboard focus when coming from displayName
                let textInputSteps = State.ProfileSetupStep.textInputSteps
                if textInputSteps.contains(previousStep) {
                    state.usernameState?.isFocused = true
                }
                return .none

            case .birthday:
                state.birthdayState = MadlibsBirthdayEntry.State(me: state.me)
                state.previousStep = state.currentStep
                state.navigationDirection = navigationDirection
                state.currentStep = step
                return .none
            }

        case .goToPrevious:
            guard let previousStep = state.currentStep.previous else {
                return .none // Should never happen
            }

            resetFocusStatesConditionally(state: &state, to: previousStep)

            // Remove current and future steps from completed set when going backward
            if let currentIndex = state.allSteps.firstIndex(of: state.currentStep) {
                let stepsToRemove = Set(state.allSteps.suffix(from: currentIndex))
                state.completedSteps.subtract(stepsToRemove)
            }

            state.previousStep = state.currentStep
            state.navigationDirection = .backward
            state.currentStep = previousStep

            return .none

        case .completeFlow(let me):
            state.isRequestingNotificationPermission = false

            return .merge(
                .cancel(id: NotificationStatusCancellable()),
                .send(.delegate(.profileSetupCompleted(me)))
            )
        }
    }

    func handleMusicGenreDelegate(
        _ delegateAction: MusicGenrePicker.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let currentStep = state.currentStep

        switch delegateAction {
        case .onAppear:
            return .send(.onStepAppear(state.currentStep))

        case .genreSelected:
            return .none

        case .maxSelectionAttempted:
            return .none

        case .savedGenresToUserConfig:
            switch state.currentStep {
            case .musicGenre:
                state.completedSteps.insert(state.currentStep)
                let selectedGenres = state.musicGenreState.genres.filter(\.isSelected).map(\.name).joined(separator: ", ")
                // Track music genres update succeeded
                onboardingAnalyticsClient.trackMadlibsEvent(.musicGenresUpdateSucceeded(
                    currentStep: currentStep.toMadlibsStep,
                    genres: selectedGenres
                ))
                return navigateToNextOrComplete(from: currentStep, me: state.me)

            case .displayName,
                 .username,
                 .birthday,
                 .notifications:
                return .none
            }

        case .errorSavingGenresToUserConfig:
            switch state.currentStep {
            case .musicGenre:
                // Track music genres update failed
                onboardingAnalyticsClient.trackMadlibsEvent(.musicGenresUpdateFailed(currentStep: .musicGenre))
                state.completedSteps.insert(currentStep)
                return navigateToNextOrComplete(from: currentStep, me: state.me)

            case .displayName,
                 .username,
                 .birthday,
                 .notifications:
                return .none
            }
        }
    }

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

        switch delegateAction {
        case .onAppear:
            return .send(.onStepAppear(currentStep))

        case .updateNameComplete(let me):
            state.me = me
            state.completedSteps.insert(.displayName)
            onboardingAnalyticsClient.trackMadlibsEvent(.updateNameSucceeded(currentStep: currentStep.toMadlibsStep))
            return navigateToNextOrComplete(from: state.currentStep, me: state.me)

        case .updateNameFailed:
            onboardingAnalyticsClient.trackMadlibsEvent(.updateNameFailed(currentStep: currentStep.toMadlibsStep))
            return .none

        case .backTapped:
            return .send(.navigation(.goToPrevious))

        case .displayNameFieldFocused:
            return .none
        }
    }

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

        switch delegateAction {
        case .onAppear:
            return .send(.onStepAppear(currentStep))

        case .updateUsernameComplete(let me):
            state.me = me
            state.completedSteps.insert(.username)
            onboardingAnalyticsClient.trackMadlibsEvent(.updateUsernameSucceeded(
                currentStep: currentStep.toMadlibsStep,
                username: me.user.handle
            ))
            return navigateToNextOrComplete(from: currentStep, me: state.me)

        case .updateUsernameFailed:
            onboardingAnalyticsClient.trackMadlibsEvent(.updateUsernameFailed(currentStep: currentStep.toMadlibsStep))
            return .none

        case .backTapped:
            onboardingAnalyticsClient.trackMadlibsEvent(.backTapped(
                currentStep: currentStep.toMadlibsStep,
                previousStep: currentStep.previous?.toMadlibsStep
            ))
            return .send(.navigation(.goToPrevious))

        case .usernameFieldFocused:
            return .none
        }
    }

    func handleBirthdayDelegate(
        _ delegateAction: MadlibsBirthdayEntry.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let currentStep = state.currentStep

        switch delegateAction {
        case .updateBirthdayComplete(let me):
            state.me = me
            state.completedSteps.insert(.birthday)
            onboardingAnalyticsClient.trackMadlibsEvent(.birthdayUpdateSucceeded(currentStep: currentStep.toMadlibsStep))
            return navigateToNextOrComplete(from: currentStep, me: state.me)

        case .updateBirthdayFailed:
            onboardingAnalyticsClient.trackMadlibsEvent(.birthdayUpdateFailed(currentStep: currentStep.toMadlibsStep))
            return .none

        case .onAppear:
            return .send(.onStepAppear(currentStep))
        }
    }

    func handleNotificationsDelegate(
        _ delegateAction: OnboardingNotificationsQuestion.Action.Delegate,
        state: inout State
    ) -> Effect<Action> {
        let currentStep = state.currentStep
        let nextRoute: (Me) -> Effect<Action> = { me in
            if let nextStep = currentStep.next {
                return .send(.navigation(.moveToStep(nextStep)))
            } else {
                return .send(.navigation(.completeFlow(me)))
            }
        }

        switch delegateAction {
        case .onAppear:
            return .send(.onStepAppear(state.currentStep))

        case .tappedBack:
            onboardingAnalyticsClient.trackMadlibsEvent(.backTapped(
                currentStep: state.currentStep.toMadlibsStep,
                previousStep: state.currentStep.previous?.toMadlibsStep
            ))
            return .send(.navigation(.goToPrevious))

        case .tappedSkip(let me):
            state.me = me
            state.completedSteps.insert(.notifications)
            onboardingAnalyticsClient.trackMadlibsEvent(.skipTapped(
                currentStep: state.currentStep.toMadlibsStep,
                nextStep: state.currentStep.next?.toMadlibsStep
            ))
            return nextRoute(me)

        case .showNotification(let me):
            state.me = me
            state.completedSteps.insert(.notifications)

            guard let currentStatus = state.notificationAllowStatus else {
                return .none
            }

            switch currentStatus {
            case .allowed,
                 .notAllowed:
                return nextRoute(me)

            case .unknown:
                let currentStep = state.currentStep

                return .run { send in
                    // Set loading state to true
                    await send(.internal(.notificationPermissionRequestStarted))

                    do {
                        let statusBefore = await userNotificationsClient.getNotificationStatus()
                        let granted = try await userNotificationsClient.requestAuthorization([.alert, .badge, .sound])
                        let statusAfter = await userNotificationsClient.getNotificationStatus()

                        let permissionsDidNotChange = statusBefore == statusAfter
                        let permissionWasNotGranted = !granted
                        let systemNotificationAlertWasShown = !(permissionsDidNotChange && permissionWasNotGranted)
                        if systemNotificationAlertWasShown {
                            // Track notification permission request and result
                            onboardingAnalyticsClient.trackMadlibsEvent(
                                .notificationsPermissionRequested(
                                    currentStep: currentStep.toMadlibsStep
                                )
                            )

                            onboardingAnalyticsClient.trackMadlibsEvent(
                                .notificationsPermissionResult(
                                    currentStep: currentStep.toMadlibsStep,
                                    allowed: granted
                                )
                            )
                        }

                        if let nextStep = currentStep.next {
                            await send(.navigation(.moveToStep(nextStep)))
                        } else {
                            await send(.internal(.willSendCompleteFlow))
                            await send(.navigation(.completeFlow(me)))
                        }

                        await send(.internal(.notificationPermissionRequestCompleted(me)))
                    } catch {
                        await send(.internal(.notificationPermissionRequestCompleted(me)))

                        if let nextStep = currentStep.next {
                            await send(.navigation(.moveToStep(nextStep)))
                        } else {
                            await send(.internal(.willSendCompleteFlow))
                            await send(.navigation(.completeFlow(me)))
                        }
                    }
                }
            }
        }
    }

    func handleInternalAction(
        _ internalAction: Action.Internal,
        state: inout State
    ) -> Effect<Action> {
        switch internalAction {
        case .notificationStatusUpdate(let status):
            state.notificationAllowStatus = status
            return .none

        case .notificationPermissionRequestStarted:
            state.isRequestingNotificationPermission = true
            return .none

        case .notificationPermissionRequestCompleted(let me):
            state.isRequestingNotificationPermission = false
            state.me = me
            return .none

        case .willSendCompleteFlow:
            state.willSendCompleteFlow = true
            return .none
        }
    }

    func navigateToNextOrComplete(
        from currentStep: State.ProfileSetupStep,
        me: Me
    ) -> Effect<Action> {
        if let nextStep = currentStep.next {
            return .send(.navigation(.moveToStep(nextStep)))
        } else {
            return .concatenate(
                .send(.internal(.willSendCompleteFlow)),
                .send(.navigation(.completeFlow(me)))
            )
        }
    }
}

// MARK: - Private Helpers

private extension MadlibsContainerReducer {
    /// Clears focus from all text input steps and dismisses keyboard to prevent UI glitches during navigation
    func resetFocusStates(state: inout State) {
        state.displayNameState?.isFocused = false
        state.usernameState?.isFocused = false

        UIApplication.shared.sendAction(
            #selector(UIResponder.resignFirstResponder),
            to: nil,
            from: nil,
            for: nil
        )
    }

    /// Smart focus management that maintains keyboard between text input steps
    func resetFocusStatesConditionally(state: inout State, to nextStep: State.ProfileSetupStep) {
        // Only dismiss keyboard if we're not transitioning between text input steps
        if !state.shouldMaintainKeyboard(to: nextStep) {
            state.displayNameState?.isFocused = false
            state.usernameState?.isFocused = false

            UIApplication.shared.sendAction(
                #selector(UIResponder.resignFirstResponder),
                to: nil,
                from: nil,
                for: nil
            )
        }
    }
}

extension MadlibsContainerReducer.State.ProfileSetupStep {
    static var textInputSteps: [Self] { allCases.filter(\.isTextInputStep) }

    var isTextInputStep: Bool {
        switch self {
        case .displayName, .username, .birthday:
            true
        case .musicGenre, .notifications:
            false
        }
    }
}
