import APIClient
import ComposableArchitecture
import FeatureMadlibsOnboarding
import FeatureOnboardingModels
import FeatureSocial
import Foundation
import Localization
import OnboardingAnalyticsClient
import Sharing
import StatsigClient
import UserNotificationsClient
import Utilities

// MARK: - OnboardingError

public enum OnboardingError: LocalizedError, Equatable {
    case getMeFailed

    public var errorDescription: String? {
        switch self {
        case .getMeFailed:
            L10n.FeatureOnboarding.errorGetMeFailed
        }
    }
}

// MARK: - OnboardingReducer

@Reducer
public struct OnboardingReducer {
    @Reducer(state: .equatable)
    public enum Path {
        case signUp(SignUp)
        case phoneSignIn(PhoneSignIn)
        case phoneCode(PhoneCodeEntry)

        case displayName(NameEntry)
        case username(UsernameEntry)
        case birthday(BirthdayEntry)

        case notifications(OnboardingNotificationsQuestion)
        case contactsAuth(ContactsAuthorization)
        case contactSync(ContactSync)

        case madlibsFlow
    }

    public struct GetMeResponseData: Equatable {
        let me: Me
        let isFirstTimeUser: Bool
    }

    public struct ExperimentFlags: Equatable {
        struct MadlibsOnboarding: Equatable {
            let isEnabled: Bool
        }

        let madlibs: MadlibsOnboarding
        let onboardingFlow: OnboardingFlowVariant
    }

    // MARK: - State

    @ObservableState
    public struct State: Equatable {
        public var isOnboardingStarted: Bool = false
        public var hasExperimentFlags: Bool = false

        public var path: StackState<Path.State> = .init()

        public var initialMethod: SignUp.Method
        public var signUp: SignUp.State
        public var currentStep: OnboardingStepType?

        public var flowVariant: OnboardingFlowVariant = .default
        public var experimentFlags: ExperimentFlags?

        /// The current user's profile data fetched from the API after authentication.
        /// This is populated after successful authentication and updated as the user
        /// completes onboarding steps (e.g., setting display name, username, birthday).
        public var currentMe: Me?

        /// The user's full name obtained from the authentication provider (e.g., Google, Apple).
        /// This value is temporarily stored during authentication and used to pre-populate
        /// the display name field during onboarding if the user doesn't already have one set.
        public var fullNameFromAuth: String?
        public var isFTUX = false

        @ObservationStateIgnored @ObservedBox
        public var madlibsContainerState: MadlibsContainerReducer.State?

        public var completedSteps: Set<OnboardingStepType> = []

        @Presents public var alert: AlertState<Action.Alert>?

        @Shared(.appStorage(.contactSyncSeen)) public var contactSyncSeen: Bool = false

        /// When true, the onboarding flow will skip the welcome and sign up screens
        /// and go directly to profile setup steps (assuming user is already authenticated)
        fileprivate(set) var skipAuth = false
        fileprivate(set) var overrideFlowVariant: OnboardingFlowVariant?

        public let sessionId: String
        public let deviceId: String
        public let startTime: Date

        public init(
            flowVariant: OnboardingFlowVariant,
            deviceId: String,
            initialMethod: SignUp.Method,
            startTime: Date = Date()
        ) {
            self.flowVariant = flowVariant
            self.sessionId = UUID().uuidString
            self.deviceId = deviceId
            self.startTime = startTime
            self.initialMethod = initialMethod
            self.signUp = SignUp.State(method: initialMethod)
        }

        public static func makeMadlibsDebug(
            deviceId: String,
            startTime: Date = .now
        ) -> Self {
            var state = Self(
                flowVariant: .madlibs,
                deviceId: deviceId,
                initialMethod: .create,
                startTime: startTime
            )
            state.skipAuth = true
            state.overrideFlowVariant = .madlibs
            return state
        }
    }

    // MARK: - Action

    public enum Action {
        case onAppear

        case signUp(SignUp.Action)
        case path(StackAction<Path.State, Path.Action>)
        case madlibsContainer(MadlibsContainerReducer.Action)
        case delegate(Delegate)
        case `internal`(Internal)
        case alert(PresentationAction<Alert>)

        public enum Internal: Equatable {
            case initializeExperimentFlags
            case experimentFlagsLoaded(ExperimentFlags)
            case startOnboarding
            case getMe(isFirstTimeUser: Bool)
            case getMeResponse(Result<GetMeResponseData, OnboardingError>)
        }

        public enum Alert: Equatable {
            case dismiss
        }

        @CasePathable
        public enum Delegate: Equatable {
            case onboardingCompleted(OnboardingCompletedResult)
            case onboardingSkippedForReturningUser(Me)
            case onboardingFailed(OnboardingError)
            case backTappedOnRoot
        }
    }

    // MARK: - Dependencies

    @Dependency(\.apiClientV2) var apiClient
    @Dependency(StatsigClient.self) var statsigClient
    @Dependency(\.onboardingAnalyticsClient) var onboardingAnalyticsClient

    public init() {}

    // MARK: - Reducer Body

    public var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .onAppear:
                if state.hasExperimentFlags {
                    return .send(.internal(.startOnboarding))
                } else {
                    return .send(.internal(.initializeExperimentFlags))
                }

            case .internal(let internalAction):
                return handleInternalAction(internalAction, state: &state)

            case .signUp(.delegate(let delegateAction)):
                return handleSignUpDelegate(delegateAction, state: &state)

            case .path(.element(_, .signUp(.delegate(let delegateAction)))):
                return handleSignUpDelegate(delegateAction, state: &state)

            case .path(.element(_, .phoneSignIn(.delegate(let delegateAction)))):
                return handlePhoneSignInDelegate(delegateAction, state: &state)

            case .path(.element(_, .phoneCode(.delegate(let delegateAction)))):
                return handlePhoneCodeDelegate(delegateAction, state: &state)

            case .path(.element(_, .displayName(.delegate(let delegateAction)))):
                return handleDisplayNameDelegate(delegateAction, state: &state)

            case .path(.element(_, .username(.delegate(let delegateAction)))):
                return handleUsernameDelegate(delegateAction, state: &state)

            case .path(.element(_, .birthday(.delegate(let delegateAction)))):
                return handleBirthdayDelegate(delegateAction, state: &state)

            case .path(.element(_, .notifications(.delegate(let delegateAction)))):
                return handleNotificationsDelegate(delegateAction, state: &state)

            case .path(.element(_, .contactsAuth(.delegate(let delegateAction)))):
                return handleContactsAuthDelegate(delegateAction, state: &state)

            case .path(.element(_, .contactSync(.delegate(let delegateAction)))):
                return handleContactSyncDelegate(delegateAction, state: &state)

            case .madlibsContainer(.delegate(let delegateAction)):
                return handleMadlibsContainerDelegate(delegateAction, state: &state)

            case .signUp, .path, .madlibsContainer, .delegate:
                return .none

            case .alert(.presented(.dismiss)):
                state.alert = nil
                return .send(.delegate(.onboardingFailed(OnboardingError.getMeFailed)))

            case .alert:
                return .none
            }
        }
        .forEach(\.path, action: \.path)
        .ifLet(\.madlibsContainerState, action: \.madlibsContainer) {
            MadlibsContainerReducer()
        }
        .ifLet(\.alert, action: \.alert)

        Scope(state: \.signUp, action: \.signUp) {
            SignUp()
        }
    }
}
